├── src ├── main │ ├── resources │ │ ├── public │ │ │ ├── favicon.ico │ │ │ └── error │ │ │ │ ├── 403.html │ │ │ │ ├── error.html │ │ │ │ ├── 404.html │ │ │ │ ├── 405.html │ │ │ │ ├── 500.html │ │ │ │ └── 401.html │ │ ├── static │ │ │ ├── img │ │ │ │ ├── a1.jpg │ │ │ │ ├── a2.jpg │ │ │ │ ├── a3.jpg │ │ │ │ ├── a4.jpg │ │ │ │ ├── a5.jpg │ │ │ │ ├── a6.jpg │ │ │ │ ├── a7.jpg │ │ │ │ ├── a8.jpg │ │ │ │ ├── lou.jpg │ │ │ │ ├── p1.jpg │ │ │ │ ├── p3.jpg │ │ │ │ ├── p_big1.jpg │ │ │ │ ├── p_big2.jpg │ │ │ │ ├── profile.jpg │ │ │ │ └── profile_small.jpg │ │ │ ├── images │ │ │ │ ├── 404.jpg │ │ │ │ ├── logo_icon.png │ │ │ │ └── photos │ │ │ │ │ ├── user1.png │ │ │ │ │ ├── user2.png │ │ │ │ │ ├── user3.png │ │ │ │ │ ├── user4.png │ │ │ │ │ ├── user5.png │ │ │ │ │ └── user-avatar.png │ │ │ ├── css │ │ │ │ ├── patterns │ │ │ │ │ └── header-profile.png │ │ │ │ └── plugins │ │ │ │ │ ├── fileinput │ │ │ │ │ └── loading.gif │ │ │ │ │ ├── chosen │ │ │ │ │ └── chosen-sprite@2x.png │ │ │ │ │ ├── footable │ │ │ │ │ └── fonts │ │ │ │ │ │ ├── footable.ttf │ │ │ │ │ │ └── footable.woff │ │ │ │ │ ├── iCheck │ │ │ │ │ ├── minimal-green@2x.png │ │ │ │ │ └── green.css │ │ │ │ │ ├── tagsinput │ │ │ │ │ └── jquery.tagsinput.css │ │ │ │ │ ├── multiselect │ │ │ │ │ └── tree-multiselect.min.css │ │ │ │ │ └── gritter │ │ │ │ │ └── jquery.gritter.css │ │ │ ├── js │ │ │ │ ├── My97DatePicker │ │ │ │ │ ├── skin │ │ │ │ │ │ ├── datePicker.gif │ │ │ │ │ │ ├── default │ │ │ │ │ │ │ └── img.gif │ │ │ │ │ │ ├── whyGreen │ │ │ │ │ │ │ ├── bg.jpg │ │ │ │ │ │ │ └── img.gif │ │ │ │ │ │ └── WdatePicker.css │ │ │ │ │ └── lang │ │ │ │ │ │ ├── en.js │ │ │ │ │ │ ├── zh-cn.js │ │ │ │ │ │ └── zh-tw.js │ │ │ │ └── plugins │ │ │ │ │ ├── datetimepicker │ │ │ │ │ └── locales │ │ │ │ │ │ ├── bootstrap-datetimepicker.zh-CN.js │ │ │ │ │ │ └── bootstrap-datetimepicker.zh-TW.js │ │ │ │ │ ├── opentable │ │ │ │ │ └── opentable.js │ │ │ │ │ ├── loading │ │ │ │ │ └── jquery.loading.js │ │ │ │ │ ├── fileinput │ │ │ │ │ └── fileinput_locale_zh.js │ │ │ │ │ ├── metisMenu │ │ │ │ │ └── jquery.metisMenu.js │ │ │ │ │ ├── validate │ │ │ │ │ └── validate-cn.js │ │ │ │ │ ├── toastr │ │ │ │ │ └── toastr.min.js │ │ │ │ │ └── slimscroll │ │ │ │ │ └── jquery.slimscroll.min.js │ │ │ ├── fonts │ │ │ │ └── glyphicons-halflings-regular.woff2 │ │ │ └── font-awesome │ │ │ │ └── fonts │ │ │ │ └── fontawesome-webfont.woff2 │ │ ├── db │ │ │ └── sqlite │ │ │ │ ├── db1.sqlite3 │ │ │ │ └── db2.sqlite3 │ │ ├── conf │ │ │ ├── ehcache-shiro.xml │ │ │ └── xss_security_config.xml │ │ ├── banner.txt │ │ ├── templates │ │ │ ├── common │ │ │ │ ├── error.ftl │ │ │ │ └── page.ftl │ │ │ ├── view │ │ │ │ ├── news │ │ │ │ │ ├── list_page1.ftl │ │ │ │ │ ├── list_page2.ftl │ │ │ │ │ ├── edit_form.ftl │ │ │ │ │ └── list_page.ftl │ │ │ │ └── login │ │ │ │ │ └── login.ftl │ │ │ └── topnav.ftl │ │ ├── META-INF │ │ │ └── spring-configuration-metadata.json │ │ ├── mapper │ │ │ ├── auth │ │ │ │ ├── UserRoleMapper.xml │ │ │ │ ├── RolePermissionMapper.xml │ │ │ │ └── RoleMapper.xml │ │ │ └── simple │ │ │ │ └── NewsMapper.xml │ │ ├── log4j2.xml │ │ └── application.yml │ └── java │ │ └── cn │ │ └── springboot │ │ ├── framework │ │ ├── locks │ │ │ ├── package-info.java │ │ │ ├── exception │ │ │ │ ├── package-info.java │ │ │ │ ├── LockException.java │ │ │ │ ├── UnLockException.java │ │ │ │ └── BuildLockException.java │ │ │ ├── zookeeper │ │ │ │ ├── package-info.java │ │ │ │ └── ZookeeperMutexLock.java │ │ │ ├── ReadWriteLock.java │ │ │ ├── MutexLock.java │ │ │ ├── DistributedLockFactory.java │ │ │ ├── SharedLock.java │ │ │ └── DistributedLock.java │ │ ├── pk │ │ │ ├── TableEnum.java │ │ │ ├── FactoryAboutKey.java │ │ │ └── local │ │ │ │ ├── LocalIdGenerator.java │ │ │ │ └── impl │ │ │ │ └── LocalIdGeneratorImpl.java │ │ ├── constant │ │ │ ├── Constant.java │ │ │ ├── Constants.java │ │ │ └── CacheableKey.java │ │ ├── datasource │ │ │ ├── DataSourceTagger.java │ │ │ └── PageInfo.java │ │ └── exception │ │ │ └── BusinessException.java │ │ ├── model │ │ ├── BaseEntity.java │ │ ├── auth │ │ │ ├── UserRole.java │ │ │ ├── RolePermission.java │ │ │ ├── Role.java │ │ │ ├── Permission.java │ │ │ └── User.java │ │ └── simple │ │ │ └── News.java │ │ ├── mapper │ │ ├── auth │ │ │ ├── UserRoleMapper.java │ │ │ ├── RolePermissionMapper.java │ │ │ ├── RoleMapper.java │ │ │ ├── PermissionMapper.java │ │ │ └── UserMapper.java │ │ └── simple │ │ │ └── NewsMapper.java │ │ ├── service │ │ ├── auth │ │ │ ├── PermissionService.java │ │ │ ├── AuthService.java │ │ │ ├── RoleService.java │ │ │ ├── impl │ │ │ │ ├── AuthServiceImpl.java │ │ │ │ └── RoleServiceImpl.java │ │ │ └── UserService.java │ │ ├── simple │ │ │ ├── NewsService.java │ │ │ └── impl │ │ │ │ └── NewsServiceImpl.java │ │ └── LdapService.java │ │ ├── web │ │ ├── shiro │ │ │ ├── vo │ │ │ │ ├── PermissionVo.java │ │ │ │ ├── Principal.java │ │ │ │ └── RoleEnumUtil.java │ │ │ └── MShiroFilterFactoryBean.java │ │ └── controller │ │ │ ├── ViewController.java │ │ │ ├── MainController.java │ │ │ └── LoginController.java │ │ ├── config │ │ ├── FreemarkerConfig.java │ │ ├── authority │ │ │ ├── service │ │ │ │ └── xss │ │ │ │ │ ├── XSSSecurityConfig.java │ │ │ │ │ └── XSSSecurityConstants.java │ │ │ └── filter │ │ │ │ ├── SQLInjectionFilterServlet.java │ │ │ │ └── XSSSecurityFilter.java │ │ ├── LdapConfig.java │ │ ├── ServletContextConfig.java │ │ ├── DistributedLockConfig.java │ │ ├── MybatisConfig.java │ │ └── ThreadPoolConfig.java │ │ ├── util │ │ ├── salt │ │ │ ├── Encodes.java │ │ │ ├── Exceptions.java │ │ │ └── Digests.java │ │ └── UUIDUtil.java │ │ └── Application.java └── test │ ├── resources │ └── template │ │ ├── mapper.java.ftl │ │ └── entity.java.ftl │ └── java │ ├── cn │ └── springboot │ │ ├── service │ │ └── LdapServiceTest.java │ │ ├── ApplicationTests.java │ │ └── InitServiceTest.java │ └── org │ └── mybatis │ └── generator │ └── SqlServerTypeConvertCustom.java ├── version.bat ├── .gitignore ├── .gitattributes ├── version.sh └── README.md /src/main/resources/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/public/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/static/img/a1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/img/a1.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/a2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/img/a2.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/a3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/img/a3.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/a4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/img/a4.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/a5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/img/a5.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/a6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/img/a6.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/a7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/img/a7.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/a8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/img/a8.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/lou.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/img/lou.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/p1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/img/p1.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/p3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/img/p3.jpg -------------------------------------------------------------------------------- /src/main/resources/db/sqlite/db1.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/db/sqlite/db1.sqlite3 -------------------------------------------------------------------------------- /src/main/resources/db/sqlite/db2.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/db/sqlite/db2.sqlite3 -------------------------------------------------------------------------------- /src/main/resources/static/images/404.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/images/404.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/p_big1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/img/p_big1.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/p_big2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/img/p_big2.jpg -------------------------------------------------------------------------------- /version.bat: -------------------------------------------------------------------------------- 1 | @title springboot-freemarker 2 | @color 0a 3 | 4 | call mvn versions:set -DgenerateBackupPoms=false -DnewVersion=2022.0.1 5 | pause 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.externalToolBuilders 3 | /*.log 4 | /.classpath 5 | /.project 6 | /.settings 7 | /*.db 8 | /bin 9 | /logs 10 | /*.iml -------------------------------------------------------------------------------- /src/main/resources/static/img/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/img/profile.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/logo_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/images/logo_icon.png -------------------------------------------------------------------------------- /src/main/resources/static/img/profile_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/img/profile_small.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/photos/user1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/images/photos/user1.png -------------------------------------------------------------------------------- /src/main/resources/static/images/photos/user2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/images/photos/user2.png -------------------------------------------------------------------------------- /src/main/resources/static/images/photos/user3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/images/photos/user3.png -------------------------------------------------------------------------------- /src/main/resources/static/images/photos/user4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/images/photos/user4.png -------------------------------------------------------------------------------- /src/main/resources/static/images/photos/user5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/images/photos/user5.png -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/locks/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | *

提供了分布式锁的接口定义类

3 | * 4 | * @author jqxi 5 | */ 6 | package cn.springboot.framework.locks; 7 | -------------------------------------------------------------------------------- /src/main/resources/static/images/photos/user-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/images/photos/user-avatar.png -------------------------------------------------------------------------------- /src/main/resources/static/css/patterns/header-profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/css/patterns/header-profile.png -------------------------------------------------------------------------------- /src/main/resources/static/css/plugins/fileinput/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/css/plugins/fileinput/loading.gif -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/locks/exception/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | *

提供了分布式锁接口所抛出的异常类

3 | * 4 | * @author jqxi 5 | */ 6 | package cn.springboot.framework.locks.exception; 7 | -------------------------------------------------------------------------------- /src/main/resources/static/css/plugins/chosen/chosen-sprite@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/css/plugins/chosen/chosen-sprite@2x.png -------------------------------------------------------------------------------- /src/main/resources/static/css/plugins/footable/fonts/footable.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/css/plugins/footable/fonts/footable.ttf -------------------------------------------------------------------------------- /src/main/resources/static/css/plugins/iCheck/minimal-green@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/css/plugins/iCheck/minimal-green@2x.png -------------------------------------------------------------------------------- /src/main/resources/static/js/My97DatePicker/skin/datePicker.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/js/My97DatePicker/skin/datePicker.gif -------------------------------------------------------------------------------- /src/main/resources/static/js/My97DatePicker/skin/default/img.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/js/My97DatePicker/skin/default/img.gif -------------------------------------------------------------------------------- /src/main/resources/static/js/My97DatePicker/skin/whyGreen/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/js/My97DatePicker/skin/whyGreen/bg.jpg -------------------------------------------------------------------------------- /src/main/resources/static/js/My97DatePicker/skin/whyGreen/img.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/js/My97DatePicker/skin/whyGreen/img.gif -------------------------------------------------------------------------------- /src/main/resources/static/css/plugins/footable/fonts/footable.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/css/plugins/footable/fonts/footable.woff -------------------------------------------------------------------------------- /src/main/resources/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobeflyboy/springboot-freemarker/HEAD/src/main/resources/static/font-awesome/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=Java 2 | *.css linguist-language=Java 3 | *.html linguist-language=Java 4 | *.ftl linguist-language=Java 5 | *.FreeMarker linguist-language=Java 6 | *.freemarker linguist-language=Java -------------------------------------------------------------------------------- /version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cat springboot-freemarker/src/main/resources/banner.txt 3 | # 默认版本号 4 | DEFAULT_VERSION=2022.0.1 5 | # 接输入参数做为新版本号 6 | VERSION=${1:-$DEFAULT_VERSION} 7 | # 更新版本号 8 | mvn versions:set -DgenerateBackupPoms=false -DnewVersion=$VERSION 9 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/locks/zookeeper/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | *

提供了基于 ZooKeeper 的分布式锁实现, 3 | * 通过其客户端 curator 的 API 进行二次封装实现 4 | *

5 | * 6 | * @author 胡桃夹子 7 | */ 8 | package cn.springboot.framework.locks.zookeeper; 9 | -------------------------------------------------------------------------------- /src/main/resources/static/js/My97DatePicker/skin/WdatePicker.css: -------------------------------------------------------------------------------- 1 | .Wdate{ 2 | border:#999 1px solid; 3 | /*height:20px;*/ 4 | background:#fff url(datePicker.gif) no-repeat right; 5 | } 6 | .Wdate::-ms-clear{display:none;} 7 | 8 | .WdateFmtErr{ 9 | font-weight:bold; 10 | color:red; 11 | } -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/pk/TableEnum.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.pk; 2 | 3 | /** 4 | * @author 胡桃夹子 5 | * @Description 数据表名 6 | * @date Apr 12, 2017 2:12:02 PM 7 | */ 8 | public enum TableEnum { 9 | 10 | /*表名请大写*/ 11 | T_SYS_PERMISSION, 12 | T_SYS_ROLE, 13 | T_SYS_ROLE_PERMISSION, 14 | T_SYS_USER, 15 | T_SYS_USER_ROLE, 16 | T_NEWS, 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/model/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.model; 2 | 3 | import java.io.Serializable; 4 | 5 | 6 | /** 7 | * @param 8 | * @author 胡桃夹子 9 | * @Description 实体基类 10 | * @date Mar 16, 2017 3:25:15 PM 11 | */ 12 | public interface BaseEntity extends Serializable { 13 | 14 | /** 15 | * 主键 16 | * 17 | * @return 主键数据类型 18 | */ 19 | E getId(); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/constant/Constant.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.constant; 2 | 3 | /** 4 | * 常量类 5 | * 6 | * @author 胡桃夹子 7 | * @date 2021-11-17 17:56 8 | */ 9 | public class Constant { 10 | 11 | private Constant() { 12 | 13 | } 14 | 15 | public static final String BR = "\n"; 16 | 17 | /** 18 | * 数据组对外交互接口涉及的加解密商户号 19 | */ 20 | public static final String BI_ACCOUNT = "BI"; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/conf/ehcache-shiro.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/mapper/auth/UserRoleMapper.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.mapper.auth; 2 | 3 | import cn.springboot.model.auth.UserRole; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * 用户与角色关系对象 Mapper 9 | * 10 | * @author 胡桃夹子 11 | * @date 2022/3/15 14:11 12 | */ 13 | @Mapper 14 | public interface UserRoleMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/datasource/DataSourceTagger.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.datasource; 2 | 3 | /** 4 | * 数据源枚举 5 | * 6 | * @author 胡桃夹子 7 | * @date 2022/2/16 15:52 8 | */ 9 | public class DataSourceTagger { 10 | 11 | 12 | /** 13 | * 数据源1,也是默认数据源 14 | */ 15 | public static final String DB1 = "db1"; 16 | 17 | /** 18 | * 数据源2 19 | */ 20 | public static final String DB2 = "db2"; 21 | 22 | private DataSourceTagger() { 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/locks/exception/LockException.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.locks.exception; 2 | 3 | /** 4 | *

获取锁运行时异常

5 | * 6 | * @author 胡桃夹子 7 | */ 8 | public class LockException extends RuntimeException { 9 | 10 | private static final long serialVersionUID = -1881390443685916800L; 11 | 12 | public LockException(String message) { 13 | super(message); 14 | } 15 | 16 | public LockException(String message, Throwable cause) { 17 | super(message, cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/locks/exception/UnLockException.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.locks.exception; 2 | 3 | /** 4 | *

解锁运行时异常

5 | * 6 | * @author 胡桃夹子 7 | */ 8 | public class UnLockException extends RuntimeException { 9 | 10 | private static final long serialVersionUID = -2298723230022810352L; 11 | 12 | public UnLockException(String message) { 13 | super(message); 14 | } 15 | 16 | public UnLockException(String message, Throwable cause) { 17 | super(message, cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/mapper/auth/RolePermissionMapper.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.mapper.auth; 2 | 3 | import cn.springboot.model.auth.RolePermission; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * 角色与菜单关系Mapper 9 | * 10 | * @author 胡桃夹子 11 | * @date 2022/3/15 14:14 12 | */ 13 | @Mapper 14 | public interface RolePermissionMapper extends BaseMapper { 15 | 16 | RolePermission findRolePermission(RolePermission per); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/locks/exception/BuildLockException.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.locks.exception; 2 | 3 | /** 4 | *

创建锁运行时异常

5 | * 6 | * @author 胡桃夹子 7 | */ 8 | public class BuildLockException extends RuntimeException { 9 | 10 | private static final long serialVersionUID = 3875257035681233457L; 11 | 12 | public BuildLockException(String message) { 13 | super(message); 14 | } 15 | 16 | public BuildLockException(String message, Throwable cause) { 17 | super(message, cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/resources/template/mapper.java.ftl: -------------------------------------------------------------------------------- 1 | package ${package.Mapper}; 2 | 3 | import ${package.Entity}.${entity}; 4 | import ${superMapperClassPackage}; 5 | import org.springframework.stereotype.Repository; 6 | 7 | /** 8 | *

9 | * ${table.comment!} Mapper 接口 10 | *

11 | * 12 | * @author ${author} 13 | * @since ${date} 14 | */ 15 | <#if kotlin> 16 | interface ${table.mapperName} : ${superMapperClass}<${entity}> 17 | <#else> 18 | @Repository 19 | public interface ${table.mapperName} extends ${superMapperClass}<${entity}> { 20 | 21 | } 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # springboot-freemarker 介绍 # 2 | 3 |
它是一个典型的MVC三层框架,快速简单的上手。
4 | 5 | #### springboot-freemarker 6 | 7 | + 包含框架有:SpringBoot、SpringMVC、MyBaits、Bootstrap3、Freemarker; 8 | + 集成示例有:增删改查及分页;防XSS、SQL注入; 9 | 10 | 11 | #### 数据库配置 12 | + 默认是连接的MySQL数据库,支持多数据源,分别连接的db1,db2 ,在项目工程的db文件夹下有数据库初始化脚本; 13 | 14 | #### 示例启动 15 | 16 | + 启动工程; 17 | + 运行cn.springboot.Application当中的main方法; 18 | + 浏览器访问http://localhost:8090, 测试账号是admin/123456,即可看示例效果; 19 | 20 | [GitHub](https://github.com/wangxinforme) [issues](https://github.com/wangxinforme/springboot-freemarker/issues) 21 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/service/auth/PermissionService.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.service.auth; 2 | 3 | import cn.springboot.model.auth.Permission; 4 | import cn.springboot.web.shiro.vo.PermissionVo; 5 | 6 | import java.util.List; 7 | 8 | public interface PermissionService { 9 | 10 | /** 11 | * 查询用户所能访问的所有菜单 12 | * 13 | * @param userId 用户ID 14 | * @return permissions 菜单 15 | */ 16 | List getPermissions(String userId); 17 | 18 | /** 19 | * 添加 菜单 20 | * 21 | * @param permission 菜单项 22 | */ 23 | void addPermission(Permission permission); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/web/shiro/vo/PermissionVo.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.web.shiro.vo; 2 | 3 | import cn.springboot.model.auth.Permission; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Permission VO对象 9 | * 10 | * @author 胡桃夹子 11 | * @date 2022/3/15 11:04 12 | */ 13 | public class PermissionVo extends Permission { 14 | 15 | private static final long serialVersionUID = -2051933842290600230L; 16 | 17 | private List children; 18 | 19 | public List getChildren() { 20 | return children; 21 | } 22 | 23 | public void setChildren(List children) { 24 | this.children = children; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/static/js/My97DatePicker/lang/en.js: -------------------------------------------------------------------------------- 1 | var $lang={ 2 | errAlertMsg: "Invalid date or the date out of range,redo or not?", 3 | aWeekStr: ["wk", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], 4 | aLongWeekStr:["wk","Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"], 5 | aMonStr: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], 6 | aLongMonStr: ["January","February","March","April","May","June","July","August","September","October","November","December"], 7 | clearStr: "Clear", 8 | todayStr: "Today", 9 | okStr: "OK", 10 | updateStr: "OK", 11 | timeStr: "Time", 12 | quickStr: "Quick Selection", 13 | err_1: 'MinDate Cannot be bigger than MaxDate!' 14 | } -------------------------------------------------------------------------------- /src/main/resources/static/js/plugins/datetimepicker/locales/bootstrap-datetimepicker.zh-CN.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simplified Chinese translation for bootstrap-datetimepicker 3 | * Yuan Cheung 4 | */ 5 | ;(function($){ 6 | $.fn.datetimepicker.dates['zh-CN'] = { 7 | days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"], 8 | daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"], 9 | daysMin: ["日", "一", "二", "三", "四", "五", "六", "日"], 10 | months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], 11 | monthsShort: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], 12 | today: "今天", 13 | suffix: [], 14 | meridiem: ["上午", "下午"] 15 | }; 16 | }(jQuery)); 17 | -------------------------------------------------------------------------------- /src/main/resources/static/js/plugins/datetimepicker/locales/bootstrap-datetimepicker.zh-TW.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Traditional Chinese translation for bootstrap-datetimepicker 3 | * Rung-Sheng Jang 4 | */ 5 | ;(function($){ 6 | $.fn.datetimepicker.dates['zh-TW'] = { 7 | days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"], 8 | daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"], 9 | daysMin: ["日", "一", "二", "三", "四", "五", "六", "日"], 10 | months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], 11 | monthsShort: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], 12 | today: "今天", 13 | suffix: [], 14 | meridiem: ["上午", "下午"] 15 | }; 16 | }(jQuery)); 17 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/mapper/simple/NewsMapper.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.mapper.simple; 2 | 3 | import cn.springboot.framework.datasource.PageInfo; 4 | import cn.springboot.model.simple.News; 5 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 6 | import org.apache.ibatis.annotations.Mapper; 7 | import org.apache.ibatis.annotations.Param; 8 | 9 | import java.util.List; 10 | 11 | 12 | /** 13 | * 新闻对象 Mapper 14 | * @author 胡桃夹子 15 | * @Description 新闻mapper接口 16 | * @date Mar 16, 2017 3:35:19 PM 17 | */ 18 | @Mapper 19 | public interface NewsMapper extends BaseMapper { 20 | 21 | List findNewsByKeywords(@Param("keywords") String keywords); 22 | 23 | PageInfo findNewsByPage(PageInfo page, @Param("keywords") String keywords); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/locks/ReadWriteLock.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.locks; 2 | 3 | /** 4 | *

读写锁接口

5 | * 6 | *

7 | * ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。 8 | * 只要没有 writer,读取锁可以由多个 reader 线程同时保持,写入锁是独占的。
9 | * 与互斥锁相比,读写锁允许对共享数据进行更高级别的并发访问。 10 | * 一次只有一个线程(writer 线程)可以修改共享数据,任何数量的线程可以同时读取共享数据(reader 线程)。
11 | * 若读锁已经存在,则写锁不能被获取,必须等待读锁释放。 12 | * 若写锁已经存在,则不同线程的读锁不能被获取,但是相同线程内的读锁可以被获取。 13 | *

14 | * 15 | * @author 胡桃夹子 16 | */ 17 | public interface ReadWriteLock { 18 | 19 | /** 20 | *

获取读锁

21 | * 22 | * @return 返回读锁对象 23 | */ 24 | MutexLock readLock(); 25 | 26 | /** 27 | *

获取写锁

28 | * 29 | * @return 返回写锁对象 30 | */ 31 | MutexLock writeLock(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/mapper/auth/RoleMapper.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.mapper.auth; 2 | 3 | import cn.springboot.model.auth.Role; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | import java.util.List; 8 | 9 | 10 | /** 11 | * 角色对象 Mapper 12 | * 13 | * @author 胡桃夹子 14 | * @date 2022/3/15 14:12 15 | */ 16 | @Mapper 17 | public interface RoleMapper extends BaseMapper { 18 | 19 | /** 20 | * 根据用户查询对应所有角色 21 | * 22 | * @param userId 用户 23 | * @return roles 所有角色 24 | */ 25 | List findRoleByUserId(String userId); 26 | 27 | /** 28 | * 根据编码查询角色 29 | * 30 | * @param code 角色编码 31 | * @return 32 | */ 33 | Role findRoleByCode(String code); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/cn/springboot/service/LdapServiceTest.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.service; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | /** 8 | * @author 胡桃夹子 9 | * @date 2022-04-24 17:48 10 | */ 11 | @SpringBootTest 12 | public class LdapServiceTest { 13 | 14 | private static final Logger LOG = LoggerFactory.getLogger(LdapServiceTest.class); 15 | 16 | //@Autowired 17 | //private LdapService ldapService; 18 | 19 | //@Test 20 | //public void ldapAuth() { 21 | // try { 22 | // boolean resp = ldapService.ldapAuth("胡桃夹子", "sh.1101"); 23 | // LOG.debug("# {}", resp); 24 | // } catch (Exception e) { 25 | // e.printStackTrace(); 26 | // } 27 | //} 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/service/simple/NewsService.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.service.simple; 2 | 3 | import cn.springboot.framework.datasource.PageInfo; 4 | import cn.springboot.model.simple.News; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author 胡桃夹子 10 | * @Description 新闻接口类 11 | * @date Mar 16, 2017 5:19:14 PM 12 | */ 13 | public interface NewsService { 14 | 15 | boolean addNews(News news); 16 | 17 | boolean editNews(News news); 18 | 19 | News findNewsById(String newsId); 20 | 21 | List findNewsByKeywords(String keywords); 22 | 23 | PageInfo findNewsByPage(Integer pageNum, String keywords); 24 | 25 | PageInfo findNewsByPage1(Integer pageNum, String keywords); 26 | 27 | PageInfo findNewsByPage2(Integer pageNum, String keywords); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/service/LdapService.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.service; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.stereotype.Service; 6 | 7 | @Service 8 | public class LdapService { 9 | 10 | private static final Logger LOG = LoggerFactory.getLogger(LdapService.class); 11 | 12 | //@Autowired 13 | //private LdapTemplate ldapTemplate; 14 | 15 | //public boolean ldapAuth(String username, String password) { 16 | // LOG.debug("# username={},password={}", username, password); 17 | // EqualsFilter filter = new EqualsFilter("sAMAccountName", username); 18 | // boolean resp = ldapTemplate.authenticate("", filter.toString(), password); 19 | // LOG.info("# ldap authenticate resp={}", resp); 20 | // return resp; 21 | //} 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/exception/BusinessException.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.exception; 2 | 3 | /** 4 | * 自定义异常类 5 | * 6 | * @author 胡桃夹子 7 | * @date 2021/11/19 13:54 8 | */ 9 | public class BusinessException extends RuntimeException { 10 | 11 | private static final long serialVersionUID = 2248546206040115304L; 12 | /** 13 | * 错误码 14 | */ 15 | private String code; 16 | 17 | public BusinessException(String message) { 18 | super(message); 19 | } 20 | 21 | public BusinessException(String code, String message) { 22 | super(message); 23 | this.code = code; 24 | } 25 | 26 | public BusinessException(String message, Throwable cause) { 27 | super(message, cause); 28 | } 29 | 30 | public String getCode() { 31 | return this.code; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/mapper/auth/PermissionMapper.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.mapper.auth; 2 | 3 | import cn.springboot.model.auth.Permission; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | import java.util.List; 8 | 9 | 10 | /** 11 | * 菜单 Mapper 12 | * 13 | * @author 胡桃夹子 14 | * @date 2022/3/15 14:12 15 | */ 16 | @Mapper 17 | public interface PermissionMapper extends BaseMapper { 18 | 19 | /** 20 | * 查询用户所能访问的所有菜单 21 | * 22 | * @param userId 用户 23 | * @return permissions 菜单 24 | */ 25 | List findPermissionByUserId(String userId); 26 | 27 | /** 28 | * 根据菜单KEY查询菜单 29 | * 30 | * @param permissionKey 菜单KEY 31 | * @return 32 | */ 33 | Permission findPermissionByKey(String permissionKey); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/config/FreemarkerConfig.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.config; 2 | 3 | import com.jagregory.shiro.freemarker.ShiroTags; 4 | import freemarker.template.Configuration; 5 | import org.springframework.beans.factory.InitializingBean; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | * 继承FreeMarkerConfigurer类,重写afterPropertiesSet()方法; 11 | * 集成shiroTags标签 12 | * 13 | * @author 胡桃夹子 14 | * @date 2022/3/15 15:37 15 | */ 16 | @Component 17 | public class FreemarkerConfig implements InitializingBean { 18 | 19 | @Autowired 20 | private Configuration configuration; 21 | 22 | @Override 23 | public void afterPropertiesSet() throws Exception { 24 | // 加上这句后,可以在页面上使用shiro标签 25 | configuration.setSharedVariable("shiro", new ShiroTags()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/web/controller/ViewController.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.web.controller; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | 9 | /** 10 | * 示例页面 controller 11 | * 12 | * @author 胡桃夹子 13 | * @date 2022/3/24 15:12 14 | */ 15 | @Controller 16 | public class ViewController { 17 | 18 | private static final Logger log = LoggerFactory.getLogger(ViewController.class); 19 | 20 | @GetMapping("view/{submenu}/{menu}") 21 | public String setconfig(@PathVariable(value = "submenu") String submenu, @PathVariable(value = "menu") String menu) { 22 | log.info("# loding view/{}/{} ", submenu, menu); 23 | return "view/" + submenu + "/" + menu; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/static/css/plugins/tagsinput/jquery.tagsinput.css: -------------------------------------------------------------------------------- 1 | div.tagsinput { border:1px solid #e5e6e7; background: #FFF; padding:5px; width:300px; height:100px; overflow-y: auto;} 2 | div.tagsinput span.tag { border: 1px solid #65CEA7; -moz-border-radius:15px; -webkit-border-radius:15px;border-radius:15px; display: block; float: left; padding:3px 15px; text-decoration:none; background: #65CEA7; color: #fff; margin-right: 5px; margin-bottom:5px;} 3 | div.tagsinput span.tag a { font-weight: bold; color: #43886e; text-decoration:none; font-size: 13px; } 4 | div.tagsinput input { width:80px; margin:0; border:1px solid transparent; padding:5px; background: transparent; color: #000; outline:0; margin-right:5px; margin-bottom:5px; } 5 | div.tagsinput div { display:block; float: left; } 6 | .tags_clear { clear: both; width: 100%; height: 0; } 7 | .not_valid {background: #FBD8DB !important; color: #90111A !important;} 8 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/service/auth/AuthService.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.service.auth; 2 | 3 | import cn.springboot.model.auth.Role; 4 | import cn.springboot.model.auth.User; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * 组装权限接口 10 | * 11 | * @author 胡桃夹子 12 | * @date 2022/3/15 14:09 13 | */ 14 | public interface AuthService { 15 | 16 | /** 17 | * 根据用户名查询用户 18 | * 19 | * @param username 用户名 20 | * @return user 用户 21 | */ 22 | User findUserByName(String username); 23 | 24 | /** 25 | * 根据角色编码查询角色 26 | * 27 | * @param roleCode 角色编码 28 | * @return 角色对象 29 | */ 30 | Role findRoleByRoleCode(String roleCode); 31 | 32 | /** 33 | * 根据角色编码查询用户 34 | * 35 | * @param roleCode 角色编码 36 | * @return user 用户 37 | */ 38 | List findUserByRoleCode(String roleCode); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | 2 | _ _ ___ _ 3 | (_) | | _ / __) | | 4 | ___ ____ ____ _ ____ ____ | | _ ___ ___ | |_ ___ | |__ ____ ____ ____ ____ ____ ____ | | _ ____ ____ 5 | /___)| _ \ / ___)| || _ \ / _ || || \ / _ \ / _ \ | _) (___)| __) / ___) / _ ) / _ )| \ / _ | / ___)| | / ) / _ ) / ___) 6 | |___ || | | || | | || | | |( ( | || |_) )| |_| || |_| || |__ | | | | ( (/ / ( (/ / | | | |( ( | || | | |< ( ( (/ / | | 7 | (___/ | ||_/ |_| |_||_| |_| \_|| ||____/ \___/ \___/ \___) |_| |_| \____) \____)|_|_|_| \_||_||_| |_| \_) \____)|_| 8 | |_| (_____| 9 | springboot-freemarker ${application.version} 10 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/config/authority/service/xss/XSSSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.config.authority.service.xss; 2 | 3 | /** 4 | * @author 胡桃夹子 5 | * @Description 安全过滤配置信息类 6 | * @date Mar 24, 2017 7:44:47 PM 7 | */ 8 | public class XSSSecurityConfig { 9 | 10 | /** 11 | * CHECK_HEADER:是否开启header校验 12 | */ 13 | public static boolean IS_CHECK_HEADER; 14 | 15 | /** 16 | * CHECK_PARAMETER:是否开启parameter校验 17 | */ 18 | public static boolean IS_CHECK_PARAMETER; 19 | 20 | /** 21 | * CHECK_URL,是否开启检查特殊url 22 | */ 23 | public static boolean IS_CHECK_URL; 24 | 25 | /** 26 | * IS_LOG:是否记录日志 27 | */ 28 | public static boolean IS_LOG; 29 | 30 | /** 31 | * IS_LOG:是否中断操作 32 | */ 33 | public static boolean IS_CHAIN; 34 | 35 | /** 36 | * REPLACE:是否开启替换 37 | */ 38 | public static boolean REPLACE; 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/resources/templates/common/error.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 错误 5 | 6 | 7 |
8 |
9 |
10 |
11 |

12 | 页面错误,error 13 |

14 |
15 |

16 | ${err!'请求出错'},再试一次或者,或使用浏览器的返回按钮,导航到您之前访问的网页。 17 |

18 |

19 | 或者您可以点击下面这个小按钮: 20 |

21 | 返回首页 22 |
23 |
24 | 25 |
26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/web/shiro/vo/Principal.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.web.shiro.vo; 2 | 3 | import cn.springboot.model.auth.Role; 4 | import cn.springboot.model.auth.User; 5 | 6 | import java.io.Serializable; 7 | import java.util.List; 8 | 9 | /** 10 | * Principal对象 11 | * 12 | * @author 胡桃夹子 13 | * @date 2022/3/15 11:05 14 | */ 15 | public class Principal implements Serializable { 16 | private static final long serialVersionUID = -6477583820961243636L; 17 | 18 | private User user; 19 | private List roles; 20 | 21 | public User getUser() { 22 | return user; 23 | } 24 | 25 | public void setUser(User user) { 26 | this.user = user; 27 | } 28 | 29 | public List getRoles() { 30 | return roles; 31 | } 32 | 33 | public void setRoles(List roles) { 34 | this.roles = roles; 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return user.getTrueName(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/locks/MutexLock.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.locks; 2 | 3 | /** 4 | *

互斥锁接口

5 | * 6 | *

7 | * 继承自分布式锁接口{@link cn.springboot.framework.locks.DistributedLock DistributedLock}, 8 | * 同一时间只有一个线程能够获取锁,其他线程必须等待它释放锁后,才能获取。 9 | *

10 | * 11 | * @author 胡桃夹子 12 | */ 13 | public interface MutexLock extends DistributedLock { 14 | 15 | /** 16 | *

获取当前线程保持此锁的次数

17 | * 18 | *

19 | * 每调用一次 lock()或tryLock()方法,当前线程保持此锁的次数增加 1。 20 | * 每调用一次unlock()方法,当前线程保持此锁的次数减 1。 21 | *

22 | * 23 | * @return 当前线程保持此锁的次数,如果此锁未被当前线程保持过,则返回 0 24 | */ 25 | int getHoldCount(); 26 | 27 | /** 28 | *

查询当前线程是否保持此锁

29 | * 30 | *

31 | * 等同于 调用{@link cn.springboot.framework.locks.MutexLock#getHoldCount() getHoldCount()}方法, 32 | * 返回值大于 0 则当前线程保持此锁。 33 | *

34 | * 35 | * @return 如果当前线程保持此锁,则返回 true;否则返回 false 36 | */ 37 | boolean isHeldByCurrentThread(); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/service/auth/RoleService.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.service.auth; 2 | 3 | import cn.springboot.model.auth.Role; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 角色相关接口 9 | * 10 | * @author 胡桃夹子 11 | * @date 2022/3/15 14:17 12 | */ 13 | public interface RoleService { 14 | 15 | /** 16 | * 添加一个角色 ,若已经存在同名角色,则不创建 17 | * 18 | * @param role 角色对象 19 | */ 20 | void addRole(Role role); 21 | 22 | /** 23 | * 根据编码查询角色 24 | * 25 | * @param code 角色编码 26 | * @return 27 | */ 28 | Role findRoleByCode(String code); 29 | 30 | /** 31 | * 根据用户查询对应所有角色 32 | * 33 | * @param userId 用户Id 34 | * @return roles 所有角色 35 | */ 36 | List findRoleByUserId(String userId); 37 | 38 | /** 39 | * 给角色授权 40 | * 41 | * @param roleCode 角色编码 42 | * @param permissionKey 授权对应的KEY 43 | */ 44 | void addRolePermission(String roleCode, String permissionKey); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/config/LdapConfig.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | 5 | /** 6 | * LDAP Configuration 7 | * 8 | * @author 胡桃夹子 9 | * @date 2022-04-24 17:44 10 | */ 11 | //@Configuration 12 | public class LdapConfig { 13 | @Value("${spring.ldap.urls}") 14 | private String ldapUrl; 15 | @Value("${spring.ldap.username}") 16 | private String userName; 17 | @Value("${spring.ldap.password}") 18 | private String passWord; 19 | @Value("${spring.ldap.base}") 20 | private String base; 21 | 22 | //@Bean 23 | //public LdapContextSource ldapContextSource() { 24 | // LdapContextSource source = new LdapContextSource(); 25 | // source.setBase(base); 26 | // source.setUrl(ldapUrl); 27 | // source.setPassword(passWord); 28 | // source.setUserDn(userName); 29 | // return source; 30 | //} 31 | 32 | //@Bean 33 | //public LdapTemplate ldapTemplate() { 34 | // return new LdapTemplate(ldapContextSource()); 35 | //} 36 | } 37 | -------------------------------------------------------------------------------- /src/main/resources/static/js/plugins/opentable/opentable.js: -------------------------------------------------------------------------------- 1 | (function($){ 2 | $.fn.tableopen = function(ev){ 3 | var defaults = { 4 | 'template':"" 5 | }; 6 | return this.each(function () { 7 | var $this=$(this); 8 | $this.find('tbody tr').addClass('clicktr').after(defaults.template); 9 | $this.on('click','tr .view',function(){ 10 | var parent=$(this).parents('tr') 11 | var content=$('td:last-child',parent).html(); 12 | parent.next().find('td').css({'border-top':'0px'}).html(content); 13 | parent.next().toggle(100,function(){ 14 | if(parent.next().is(':hidden')){ 15 | $('td:first-child',parent).find('a').html('') 16 | }else{ 17 | $('td:first-child',parent).find('a').html('') 18 | } 19 | }); 20 | }); 21 | })} 22 | })(jQuery); -------------------------------------------------------------------------------- /src/main/resources/static/js/plugins/loading/jquery.loading.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by zifan on 2015/11/13. 3 | */ 4 | (function($){ 5 | var Loading=function(ele,ev){ 6 | this.$element = $(ele); 7 | this.options = ev; 8 | this.template='
'; 9 | 10 | }; 11 | 12 | Loading.prototype.show=function(e){ 13 | this.$element.css({"position":"relative","min-height":"200px"}) 14 | this.$element.prepend(this.template); 15 | }; 16 | 17 | Loading.prototype.hide=function(e){ 18 | var loadele=this.$element.find(".spiner-example") 19 | loadele.remove() 20 | }; 21 | 22 | $.fn.loading = function(ev){ 23 | 24 | return this.each(function () { 25 | //typeof ev == 'object' && ev; 26 | var $this = $(this) 27 | 28 | var data=new Loading(this,ev); 29 | if (typeof ev == 'string') data[ev]() 30 | else alert('加载动画,参数错误!') 31 | }) 32 | } 33 | })(jQuery); -------------------------------------------------------------------------------- /src/main/java/cn/springboot/util/salt/Encodes.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.util.salt; 2 | 3 | import org.apache.commons.codec.DecoderException; 4 | import org.apache.commons.codec.binary.Hex; 5 | 6 | /** 7 | * @author 胡桃夹子 8 | * @Description 封装各种格式的编码解码工具类
9 | * 1.Commons-Codec的 hex/base64 编码
10 | * 2.自制的base62 编码
11 | * 3.Commons-Lang的xml/html escape
12 | * 4.JDK提供的URLEncoder 13 | * @date Apr 12, 2017 9:37:43 AM 14 | */ 15 | public class Encodes { 16 | 17 | private static final String DEFAULT_URL_ENCODING = "UTF-8"; 18 | private static final char[] BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray(); 19 | 20 | /** 21 | * Hex编码. 22 | */ 23 | public static String encodeHex(byte[] input) { 24 | return Hex.encodeHexString(input); 25 | } 26 | 27 | /** 28 | * Hex解码. 29 | */ 30 | public static byte[] decodeHex(String input) { 31 | try { 32 | return Hex.decodeHex(input.toCharArray()); 33 | } catch (DecoderException e) { 34 | throw Exceptions.unchecked(e); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/cn/springboot/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cn.springboot; 2 | 3 | import cn.springboot.model.simple.News; 4 | import cn.springboot.service.simple.NewsService; 5 | import org.junit.jupiter.api.Test; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | 11 | import java.util.Calendar; 12 | 13 | @SpringBootTest 14 | public class ApplicationTests { 15 | 16 | private static final Logger log = LoggerFactory.getLogger(ApplicationTests.class); 17 | 18 | @Autowired 19 | private NewsService newsService; 20 | 21 | @Test 22 | public void addNews() { 23 | log.info("# 生产测试数据"); 24 | News news = null; 25 | for (int i = 1; i < 1001; i++) { 26 | news = new News(); 27 | news.setTitle("db1_test_" + i); 28 | news.setDescription("db1_test_" + i); 29 | news.setAddress("db1_test_" + i); 30 | news.setNewsTime(Calendar.getInstance().getTime()); 31 | newsService.addNews(news); 32 | } 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/locks/DistributedLockFactory.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.locks; 2 | 3 | /** 4 | *

分布式锁工厂接口

5 | * 6 | *

7 | * 为了方便分布式锁的使用,所有分布式锁的实例对象全部由分布式锁工厂负责提供。 8 | * 不能单独构造实例化分布式锁对象。 9 | *

10 | * 11 | *

12 | * 异常处理 :构造分布式锁实例时,任何错误都会导致抛出运行时异常 {@link cn.springboot.framework.locks.exception.BuildLockException BuildLockException} 13 | *

14 | * 15 | * @author 胡桃夹子 16 | */ 17 | public interface DistributedLockFactory { 18 | 19 | /** 20 | *

创建互斥锁实例对象

21 | * 22 | * @param name 锁的名称 23 | * @return 互斥锁实例对象 24 | * @see MutexLock 25 | */ 26 | MutexLock buildMutexLock(String name); 27 | 28 | /** 29 | *

创建读写锁实例对象

30 | * 31 | * @param name 锁的名称 32 | * @return 读写锁实例对象 33 | * @see ReadWriteLock 34 | */ 35 | ReadWriteLock buildReadWriteLock(String name); 36 | 37 | /** 38 | *

创建共享锁实例对象

39 | * 40 | * @param name 锁的名称 41 | * @param poolSize 共享池的大小,必须大于等于 1 42 | * @return 共享锁实例对象 43 | * @see SharedLock 44 | */ 45 | SharedLock buildSharedLock(String name, int poolSize); 46 | } 47 | -------------------------------------------------------------------------------- /src/main/resources/static/js/My97DatePicker/lang/zh-cn.js: -------------------------------------------------------------------------------- 1 | var $lang={ 2 | errAlertMsg: "\u4E0D\u5408\u6CD5\u7684\u65E5\u671F\u683C\u5F0F\u6216\u8005\u65E5\u671F\u8D85\u51FA\u9650\u5B9A\u8303\u56F4,\u9700\u8981\u64A4\u9500\u5417?", 3 | aWeekStr: ["\u5468","\u65E5","\u4E00","\u4E8C","\u4E09","\u56DB","\u4E94","\u516D"], 4 | aLongWeekStr:["\u5468","\u661F\u671F\u65E5","\u661F\u671F\u4E00","\u661F\u671F\u4E8C","\u661F\u671F\u4E09","\u661F\u671F\u56DB","\u661F\u671F\u4E94","\u661F\u671F\u516D"], 5 | aMonStr: ["\u4E00\u6708","\u4E8C\u6708","\u4E09\u6708","\u56DB\u6708","\u4E94\u6708","\u516D\u6708","\u4E03\u6708","\u516B\u6708","\u4E5D\u6708","\u5341\u6708","\u5341\u4E00","\u5341\u4E8C"], 6 | aLongMonStr: ["\u4E00\u6708","\u4E8C\u6708","\u4E09\u6708","\u56DB\u6708","\u4E94\u6708","\u516D\u6708","\u4E03\u6708","\u516B\u6708","\u4E5D\u6708","\u5341\u6708","\u5341\u4E00\u6708","\u5341\u4E8C\u6708"], 7 | clearStr: "\u6E05\u7A7A", 8 | todayStr: "\u4ECA\u5929", 9 | okStr: "\u786E\u5B9A", 10 | updateStr: "\u786E\u5B9A", 11 | timeStr: "\u65F6\u95F4", 12 | quickStr: "\u5FEB\u901F\u9009\u62E9", 13 | err_1: '\u6700\u5C0F\u65E5\u671F\u4E0D\u80FD\u5927\u4E8E\u6700\u5927\u65E5\u671F!' 14 | } -------------------------------------------------------------------------------- /src/main/resources/static/js/My97DatePicker/lang/zh-tw.js: -------------------------------------------------------------------------------- 1 | var $lang={ 2 | errAlertMsg: "\u4E0D\u5408\u6CD5\u7684\u65E5\u671F\u683C\u5F0F\u6216\u8005\u65E5\u671F\u8D85\u51FA\u9650\u5B9A\u7BC4\u570D,\u9700\u8981\u64A4\u92B7\u55CE?", 3 | aWeekStr: ["\u5468","\u65E5","\u4E00","\u4E8C","\u4E09","\u56DB","\u4E94","\u516D"], 4 | aLongWeekStr:["\u5468","\u661F\u671F\u65E5","\u661F\u671F\u4E00","\u661F\u671F\u4E8C","\u661F\u671F\u4E09","\u661F\u671F\u56DB","\u661F\u671F\u4E94","\u661F\u671F\u516D"], 5 | aMonStr: ["\u4E00\u6708","\u4E8C\u6708","\u4E09\u6708","\u56DB\u6708","\u4E94\u6708","\u516D\u6708","\u4E03\u6708","\u516B\u6708","\u4E5D\u6708","\u5341\u6708","\u5341\u4E00","\u5341\u4E8C"], 6 | aLongMonStr: ["\u4E00\u6708","\u4E8C\u6708","\u4E09\u6708","\u56DB\u6708","\u4E94\u6708","\u516D\u6708","\u4E03\u6708","\u516B\u6708","\u4E5D\u6708","\u5341\u6708","\u5341\u4E00\u6708","\u5341\u4E8C\u6708"], 7 | clearStr: "\u6E05\u7A7A", 8 | todayStr: "\u4ECA\u5929", 9 | okStr: "\u78BA\u5B9A", 10 | updateStr: "\u78BA\u5B9A", 11 | timeStr: "\u6642\u9593", 12 | quickStr: "\u5FEB\u901F\u9078\u64C7", 13 | err_1: '\u6700\u5C0F\u65E5\u671F\u4E0D\u80FD\u5927\u65BC\u6700\u5927\u65E5\u671F!' 14 | } -------------------------------------------------------------------------------- /src/main/java/cn/springboot/config/ServletContextConfig.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.config; 2 | 3 | import cn.springboot.util.DateUtil; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.web.context.ServletContextAware; 8 | 9 | import javax.servlet.ServletContext; 10 | import java.util.Calendar; 11 | 12 | /** 13 | * 应用级参数配置 14 | * 15 | * @author 胡桃夹子 16 | * @date 2022/2/16 14:03 17 | */ 18 | @Component 19 | public class ServletContextConfig implements ServletContextAware { 20 | 21 | private static final Logger log = LoggerFactory.getLogger(ServletContextConfig.class); 22 | 23 | @Override 24 | public void setServletContext(ServletContext context) { 25 | String datetime = DateUtil.dateToString(Calendar.getInstance().getTime(), DateUtil.fm_yyyyMMddHHmmssSSS); 26 | String contextPath = context.getContextPath(); 27 | log.info("# version={} , contextPath={}", datetime, contextPath); 28 | context.setAttribute("version_css", datetime); 29 | context.setAttribute("version_js", datetime); 30 | context.setAttribute("ctx", contextPath); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/pk/FactoryAboutKey.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.pk; 2 | 3 | import cn.springboot.framework.pk.local.LocalIdGenerator; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.annotation.PostConstruct; 10 | 11 | @Component 12 | public class FactoryAboutKey { 13 | 14 | private static final Logger log = LoggerFactory.getLogger(FactoryAboutKey.class); 15 | 16 | private static FactoryAboutKey factoryAboutKey; 17 | 18 | @Autowired 19 | private LocalIdGenerator localIdGenerator; 20 | 21 | @PostConstruct 22 | public void init() throws Exception { 23 | factoryAboutKey = this; 24 | factoryAboutKey.localIdGenerator = this.localIdGenerator; 25 | } 26 | 27 | /** 28 | * 根据表名从本地内存读取所对应的id 29 | * 30 | * @param pk 表枚举配置项 31 | * @return 表新产生的ID 32 | */ 33 | public static String getPK(TableEnum pk) { 34 | String finalId = factoryAboutKey.localIdGenerator.nextUniqueId(2, 3); 35 | log.info("# PK: [{}]=[{}]", pk.name(), finalId); 36 | return finalId; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring-configuration-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "hints": [], 3 | "groups": [ 4 | { 5 | "name": "locks.zookeeper", 6 | "description": "zookeeper分布式锁相关配置参数" 7 | }, 8 | { 9 | "name": "app", 10 | "description": "应用相关参数配置" 11 | } 12 | ], 13 | "properties": [ 14 | { 15 | "name": "locks.zookeeper.namespace", 16 | "type": "java.lang.String", 17 | "description": "zookeeper分布式锁命名空间" 18 | }, 19 | { 20 | "name": "locks.zookeeper.connect-server", 21 | "type": "java.lang.String", 22 | "description": "zookeeper分布式锁连接地址" 23 | }, 24 | { 25 | "name": "locks.zookeeper.connection-timeout", 26 | "type": "java.lang.Integer", 27 | "description": "zookeeper分布式锁连接超时时间" 28 | }, 29 | { 30 | "name": "locks.zookeeper.session-timeout", 31 | "type": "java.lang.Integer", 32 | "description": "zookeeper分布式锁session会话超时时间" 33 | }, 34 | { 35 | "name": "app.version", 36 | "type": "java.lang.String", 37 | "description": "应用版本号" 38 | }, 39 | { 40 | "name": "app.white.list-url", 41 | "type": "java.util.List", 42 | "description": "HTTP拦截器白名单URN" 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/service/auth/impl/AuthServiceImpl.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.service.auth.impl; 2 | 3 | import cn.springboot.mapper.auth.RoleMapper; 4 | import cn.springboot.mapper.auth.UserMapper; 5 | import cn.springboot.model.auth.Role; 6 | import cn.springboot.model.auth.User; 7 | import cn.springboot.service.auth.AuthService; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * 组装权限接口 15 | * 16 | * @author 胡桃夹子 17 | * @date 2022/3/15 14:09 18 | */ 19 | @Service 20 | public class AuthServiceImpl implements AuthService { 21 | 22 | @Autowired 23 | private UserMapper userMapper; 24 | 25 | @Autowired 26 | private RoleMapper roleMapper; 27 | 28 | @Override 29 | public User findUserByName(String username) { 30 | return userMapper.findUserByName(username); 31 | } 32 | 33 | @Override 34 | public Role findRoleByRoleCode(String roleCode) { 35 | return roleMapper.findRoleByCode(roleCode); 36 | } 37 | 38 | @Override 39 | public List findUserByRoleCode(String roleCode) { 40 | return userMapper.findUserByRoleCode(roleCode); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/config/DistributedLockConfig.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.config; 2 | 3 | import cn.springboot.framework.locks.zookeeper.ZookeeperLockFactory; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * 分布式锁配置 10 | * 11 | * @author 胡桃夹子 12 | * @date 2022-02-18 09:02 13 | */ 14 | @Component 15 | public class DistributedLockConfig { 16 | 17 | @Value("${locks.zookeeper.namespace}") 18 | private String namespace; 19 | 20 | @Value("${locks.zookeeper.connect-server}") 21 | private String connectServer; 22 | 23 | @Value("${locks.zookeeper.connection-timeout}") 24 | private int connectionTimeout; 25 | 26 | @Value("${locks.zookeeper.session-timeout}") 27 | private int sessionTimeout; 28 | 29 | @Bean(initMethod = "init", destroyMethod = "destroy") 30 | public ZookeeperLockFactory zookeeperLockFactory() { 31 | //LOG.debug("# ZookeeperLockFactory namespace={},connectServer={},connectionTimeout={},sessionTimeout={}", namespace, connectServer, connectionTimeout, sessionTimeout); 32 | return new ZookeeperLockFactory(namespace, connectServer, connectionTimeout, sessionTimeout); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/service/auth/UserService.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.service.auth; 2 | 3 | import cn.springboot.model.auth.Role; 4 | import cn.springboot.model.auth.User; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * 用户相关接口 10 | * 11 | * @author 胡桃夹子 12 | * @date 2022/3/15 14:18 13 | */ 14 | public interface UserService { 15 | 16 | /** 17 | * 新增用户 18 | * 19 | * @param user 用户 20 | * @param role 角色 21 | */ 22 | void addUser(User user, Role role); 23 | 24 | /** 25 | * 修改密码 26 | * 27 | * @param user 当前用户 28 | */ 29 | void updatePassword(User user); 30 | 31 | /** 32 | * 根据用户名查询用户 33 | * 34 | * @param username 用户名 35 | * @return user 用户 36 | */ 37 | User findUserByName(String username); 38 | 39 | /** 40 | * 更新用户登录时间 41 | * 42 | * @param user 用户 43 | */ 44 | void updateUserLastLoginTime(User user); 45 | 46 | /** 47 | * 查询组织下所有客服员工 48 | * 49 | * @return 50 | */ 51 | List findUsers(); 52 | 53 | /** 54 | * 根据条件(组织、名称)查询用户 55 | * 56 | * @param shopId 组织ID 57 | * @param empName 用户名称 58 | * @return 59 | */ 60 | List findEmp(String shopId, String empName); 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/config/MybatisConfig.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.config; 2 | 3 | import com.baomidou.mybatisplus.annotation.DbType; 4 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 5 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.transaction.annotation.EnableTransactionManagement; 9 | 10 | /** 11 | * mybatis配置 12 | * 13 | * @author 胡桃夹子 14 | * @date 2022/2/16 14:01 15 | */ 16 | @Configuration 17 | @EnableTransactionManagement 18 | public class MybatisConfig { 19 | 20 | /** 21 | * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除) 22 | */ 23 | @Bean 24 | public MybatisPlusInterceptor mybatisPlusInterceptor() { 25 | MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 26 | PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); 27 | paginationInnerInterceptor.setDbType(DbType.MYSQL); 28 | paginationInnerInterceptor.setOverflow(true); 29 | interceptor.addInnerInterceptor(paginationInnerInterceptor); 30 | return interceptor; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/constant/Constants.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.constant; 2 | 3 | /** 4 | * @author 胡桃夹子 5 | * @Description 常量类 6 | * @date Apr 12, 2017 9:42:34 AM 7 | */ 8 | public class Constants { 9 | 10 | /** 11 | * 分页操作时,每页只显示10条 12 | */ 13 | public static final Integer PAGE_SIZE = 10; 14 | 15 | /** 16 | * 状态,1=有效,0=失效 17 | */ 18 | public static final Integer STATUS_VALID = 1; 19 | public static final Integer STATUS_INVALID = 0; 20 | 21 | /** 22 | * session & session key 23 | */ 24 | public static final String PERMISSION_SESSION = "permission_session"; 25 | public static final String SESSION_KEY = "session_key"; 26 | 27 | /** 28 | * url & roleName 29 | */ 30 | public static final String ROLE_CODE = "role_code"; 31 | public static final String PERMISSION_URL = "permission_url"; 32 | 33 | public static final String ROLE_BOSS_CODE = "boss_role"; 34 | /** 35 | * 管理员 36 | */ 37 | public static final String ROLE_MANAGER_CODE = "manager_role"; 38 | /** 39 | * 普通用户 40 | */ 41 | public static final String COMMON_ROLE_CODE = "common_role"; 42 | 43 | public static final String ROLE_BOSS_NAME = "总经理"; 44 | public static final String ROLE_MANAGER_NAME = "管理员"; 45 | public static final String ROLE_COMMON_NAME = "普通用户"; 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/constant/CacheableKey.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.constant; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * 缓存时长 8 | * 9 | * @author 胡桃夹子 10 | * @date 2020-09-21 13:12 11 | */ 12 | public class CacheableKey { 13 | 14 | /** 15 | * CacheConfig cacheNames 16 | */ 17 | public static final String BI_SECRET_KEY = "BI_SECRET_KEY"; 18 | 19 | public static final long CACHE_1_WEEK = 604800L; 20 | public static final long CACHE_1_DAY = 86400; 21 | public static final long CACHE_4_HOUR = 14400; 22 | public static final long CACHE_2_HOUR = 7200; 23 | public static final long CACHE_1_HOUR = 3600; 24 | public static final long CACHE_30_MIN = 1800; 25 | public static final long CACHE_10_MIN = 600; 26 | public static final long CACHE_5_MIN = 300; 27 | public static final long CACHE_1_MIN = 60; 28 | public static final long CACHE_10_SECOND = 10; 29 | public static final long CACHE_5_SECOND = 5; 30 | 31 | private static final Map CACHEABLE_KEY_MAP = new LinkedHashMap<>(); 32 | 33 | private CacheableKey() { 34 | 35 | } 36 | 37 | static { 38 | // 设置缓存时长 39 | CACHEABLE_KEY_MAP.put(BI_SECRET_KEY, CACHE_4_HOUR); 40 | } 41 | 42 | public static Map getCacheableKeyMap() { 43 | return CACHEABLE_KEY_MAP; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/resources/public/error/403.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 403错误 6 | 7 | 8 | 9 |
10 |
11 |
12 |
13 |

14 | 找不到页面, 15 | 16 | 403 错误 17 | 18 |

19 |
20 |

21 | 服务器拒绝请求 22 | ,再试一次或者,或使用浏览器的 23 | 返回 24 | 按钮,导航到您之前访问的网页。 25 |

26 |

27 | 或者您可以点击下面这个小按钮: 28 |

29 | 30 | 31 | 返回首页 32 | 33 |
34 |
35 | 36 |
37 |
38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/resources/public/error/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 错误 6 | 7 | 8 | 9 |
10 |
11 |
12 |
13 |

14 | 找不到页面, 15 | 16 | error 错误 17 | 18 |

19 |
20 |

21 | 请求出错 22 | ,再试一次或者,或使用浏览器的 23 | 返回 24 | 按钮,导航到您之前访问的网页。 25 |

26 |

27 | 或者您可以点击下面这个小按钮: 28 |

29 | 30 | 31 | 返回首页 32 | 33 |
34 |
35 | 36 |
37 |
38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/resources/public/error/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404错误 6 | 7 | 8 | 9 |
10 |
11 |
12 |
13 |

14 | 找不到页面, 15 | 16 | 404 错误 17 | 18 |

19 |
20 |

21 | 服务器找不到请求的网页 22 | ,再试一次或者,或使用浏览器的 23 | 返回 24 | 按钮,导航到您之前访问的网页。 25 |

26 |

27 | 或者您可以点击下面这个小按钮: 28 |

29 | 30 | 31 | 返回首页 32 | 33 |
34 |
35 | 36 |
37 |
38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/resources/public/error/405.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 405错误 6 | 7 | 8 | 9 |
10 |
11 |
12 |
13 |

14 | 找不到页面, 15 | 16 | 405 错误 17 | 18 |

19 |
20 |

21 | 禁用请求中指定的方法 22 | ,再试一次或者,或使用浏览器的 23 | 返回 24 | 按钮,导航到您之前访问的网页。 25 |

26 |

27 | 或者您可以点击下面这个小按钮: 28 |

29 | 30 | 31 | 返回首页 32 | 33 |
34 |
35 | 36 |
37 |
38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/resources/public/error/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 500错误 6 | 7 | 8 | 9 |
10 |
11 |
12 |
13 |

14 | 找不到页面, 15 | 16 | 500 错误 17 | 18 |

19 |
20 |

21 | 服务器遇到错误,无法完成请求 22 | ,再试一次或者,或使用浏览器的 23 | 返回 24 | 按钮,导航到您之前访问的网页。 25 |

26 |

27 | 或者您可以点击下面这个小按钮: 28 |

29 | 30 | 31 | 返回首页 32 | 33 |
34 |
35 | 36 |
37 |
38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/web/shiro/vo/RoleEnumUtil.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.web.shiro.vo; 2 | 3 | import cn.springboot.model.auth.Role; 4 | 5 | /** 6 | * 角色枚举 7 | * 8 | * @author Principal 9 | * @date 2022/3/15 11:05 10 | */ 11 | public enum RoleEnumUtil { 12 | 13 | ADMIN_ROLE("超级管理员", "admin_role", "超级管理员"), 14 | COMMON_ROLE("普通用户", "common_role", "普通用户"); 15 | 16 | RoleEnumUtil(String name, String roleCode, String remark) { 17 | this.name = name; 18 | this.roleCode = roleCode; 19 | this.remark = remark; 20 | } 21 | 22 | private String name; 23 | private String roleCode; 24 | private String remark; 25 | 26 | public Role getRole() { 27 | Role role = new Role(); 28 | role.setName(this.name()); 29 | role.setCode(this.roleCode); 30 | role.setRemark(this.remark); 31 | return role; 32 | } 33 | 34 | public String getName() { 35 | return name; 36 | } 37 | 38 | public void setName(String name) { 39 | this.name = name; 40 | } 41 | 42 | public String getRoleCode() { 43 | return roleCode; 44 | } 45 | 46 | public void setRoleCode(String roleCode) { 47 | this.roleCode = roleCode; 48 | } 49 | 50 | public String getRemark() { 51 | return remark; 52 | } 53 | 54 | public void setRemark(String remark) { 55 | this.remark = remark; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/model/auth/UserRole.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.model.auth; 2 | 3 | import cn.springboot.model.BaseEntity; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | 8 | 9 | /** 10 | * 用户与角色关系对象 11 | * 12 | * @author 胡桃夹子 13 | * @date 2022/3/15 14:11 14 | */ 15 | @TableName("t_sys_user_role") 16 | public class UserRole implements BaseEntity { 17 | 18 | private static final long serialVersionUID = -56720255622342923L; 19 | 20 | 21 | /** 22 | * 主键 23 | */ 24 | @TableId("id") 25 | private String id; 26 | 27 | /** 28 | * 角色表主键 29 | */ 30 | @TableField("role_id") 31 | private String roleId; 32 | 33 | /** 34 | * 用户表主键 35 | */ 36 | @TableField("user_id") 37 | private String userId; 38 | 39 | @Override 40 | public String getId() { 41 | return id; 42 | } 43 | 44 | public void setId(String id) { 45 | this.id = id; 46 | } 47 | 48 | public String getUserId() { 49 | return userId; 50 | } 51 | 52 | public void setUserId(String userId) { 53 | this.userId = userId; 54 | } 55 | 56 | public String getRoleId() { 57 | return roleId; 58 | } 59 | 60 | public void setRoleId(String roleId) { 61 | this.roleId = roleId; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/resources/public/error/401.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 401错误 6 | 7 | 8 | 9 |
10 |
11 |
12 |
13 |

14 | 找不到页面, 15 | 16 | 401 错误 17 | 18 |

19 |
20 |

21 | 请求被拒绝 22 | ,再试一次或者,或使用浏览器的 23 | 返回 24 | 按钮,导航到您之前访问的网页。 25 |

26 |

27 | 或者您可以点击下面这个小按钮: 28 |

29 | 30 | 31 | 返回首页 32 | 33 |
34 |
35 | 36 |
37 |
38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/config/authority/service/xss/XSSSecurityConstants.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.config.authority.service.xss; 2 | 3 | public class XSSSecurityConstants { 4 | 5 | /** 6 | * 配置文件标签 isCheckHeader 7 | */ 8 | public static String IS_CHECK_HEADER = "isCheckHeader"; 9 | 10 | /** 11 | * 配置文件标签 isCheckParameter 12 | */ 13 | public static String IS_CHECK_PARAMETER = "isCheckParameter"; 14 | 15 | /** 16 | * 配置文件标签 isCheckUrl 17 | */ 18 | public static String IS_CHECK_URL = "isCheckUrl"; 19 | 20 | /** 21 | * 配置文件标签 isLog 22 | */ 23 | public static String IS_LOG = "isLog"; 24 | 25 | /** 26 | * 配置文件标签 isChain 27 | */ 28 | public static String IS_CHAIN = "isChain"; 29 | 30 | /** 31 | * 配置文件标签 replace 32 | */ 33 | public static String REPLACE = "replace"; 34 | 35 | /** 36 | * 特殊url过滤标签 checkUrlList 37 | */ 38 | public static String CHECK_URL_LIST = "checkUrlList"; 39 | public static String CHECK_URL_URL = "url"; 40 | public static String CHECK_URL_REGEX = "regex"; 41 | 42 | /** 43 | * 配置文件标签 regexList 44 | */ 45 | public static String REGEX_LIST = "regexList"; 46 | 47 | /** 48 | * 替换非法字符的字符串 49 | */ 50 | public static String REPLACEMENT = ""; 51 | 52 | /** 53 | * FILTER_ERROR_PAGE:过滤后错误页面 54 | */ 55 | public static String FILTER_ERROR_PAGE = "/error"; 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/mapper/auth/UserMapper.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.mapper.auth; 2 | 3 | import cn.springboot.model.auth.User; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * 用户Mapper 12 | * 13 | * @author 胡桃夹子 14 | * @date 2022/3/15 14:13 15 | */ 16 | @Mapper 17 | public interface UserMapper extends BaseMapper { 18 | 19 | 20 | /** 21 | * 根据用户名查询用户 22 | * 23 | * @param username 用户名 24 | * @return user 用户 25 | */ 26 | User findUserByName(String username); 27 | 28 | /** 29 | * 查询组织所有用户 30 | * 31 | * @param organizeId 组织ID 32 | * @return 33 | */ 34 | List findUserByShop(String organizeId); 35 | 36 | /** 37 | * 查询组织下所有客服员工 38 | * 39 | * @return 40 | */ 41 | List findUsers(); 42 | 43 | /** 44 | * 根据条件(组织、名称)查询用户 45 | * 46 | * @param shopId 组织ID 47 | * @param empName 用户名称 48 | * @return 49 | */ 50 | List findEmp(String roleCode, Integer status, String shopId, String empName); 51 | 52 | /** 53 | * 根据用户名查询用户 54 | * 55 | * @param username 用户名 56 | * @return user 用户 57 | */ 58 | List findUserByRoleCode(@Param("roleCode") String username); 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/model/auth/RolePermission.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.model.auth; 2 | 3 | import cn.springboot.model.BaseEntity; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | 8 | /** 9 | * 角色与菜单关系对象 10 | * 11 | * @author 胡桃夹子 12 | * @date 2022/3/15 14:14 13 | */ 14 | @TableName("t_sys_role_permission") 15 | public class RolePermission implements BaseEntity { 16 | 17 | private static final long serialVersionUID = -7948507636703811294L; 18 | 19 | /** 20 | * 主键 21 | */ 22 | @TableId("id") 23 | private String id; 24 | 25 | /** 26 | * 菜单表主键 27 | */ 28 | @TableField("permission_id") 29 | private String permissionId; 30 | 31 | /** 32 | * 角色表主键 33 | */ 34 | @TableField("role_id") 35 | private String roleId; 36 | 37 | @Override 38 | public String getId() { 39 | return id; 40 | } 41 | 42 | public void setId(String id) { 43 | this.id = id; 44 | } 45 | 46 | public String getRoleId() { 47 | return roleId; 48 | } 49 | 50 | public void setRoleId(String roleId) { 51 | this.roleId = roleId; 52 | } 53 | 54 | public String getPermissionId() { 55 | return permissionId; 56 | } 57 | 58 | public void setPermissionId(String permissionId) { 59 | this.permissionId = permissionId; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/util/salt/Exceptions.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.util.salt; 2 | 3 | import java.io.PrintWriter; 4 | import java.io.StringWriter; 5 | 6 | /** 7 | * @author 胡桃夹子 8 | * @Description 关于异常的工具类 9 | * @date Apr 12, 2017 9:39:38 AM 10 | */ 11 | public class Exceptions { 12 | 13 | /** 14 | * 将CheckedException转换为UncheckedException. 15 | */ 16 | public static RuntimeException unchecked(Exception e) { 17 | if (e instanceof RuntimeException) { 18 | return (RuntimeException) e; 19 | } else { 20 | return new RuntimeException(e); 21 | } 22 | } 23 | 24 | /** 25 | * 将ErrorStack转化为String. 26 | */ 27 | public static String getStackTraceAsString(Exception e) { 28 | StringWriter stringWriter = new StringWriter(); 29 | e.printStackTrace(new PrintWriter(stringWriter)); 30 | return stringWriter.toString(); 31 | } 32 | 33 | /** 34 | * 判断异常是否由某些底层的异常引起. 35 | */ 36 | @SuppressWarnings("unchecked") 37 | public static boolean isCausedBy(Exception ex, Class... causeExceptionClasses) { 38 | Throwable cause = ex.getCause(); 39 | while (cause != null) { 40 | for (Class causeClass : causeExceptionClasses) { 41 | if (causeClass.isInstance(cause)) { 42 | return true; 43 | } 44 | } 45 | cause = cause.getCause(); 46 | } 47 | return false; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/resources/templates/view/news/list_page1.ftl: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <#if page?? && page.records?? && (page.records?size > 0) > 15 | <#list page.records as n> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
标题内容地址发生时间创建时间
${n.title }${n.description }${n.address }${n.newsTime?string("yyyy-MM-dd HH:mm") }${n.createTime?string("yyyy-MM-dd HH:mm") }
27 |
28 | 29 |
30 | 31 | 32 | 33 | <#assign formId = "newsPageForm"> 34 | <#assign showPageId = "ibox"> 35 | <#include "/common/page.ftl"/> 36 |
37 |
38 | -------------------------------------------------------------------------------- /src/main/resources/templates/view/news/list_page2.ftl: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <#if page?? && page.records?? && (page.records?size > 0) > 15 | <#list page.records as n> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
标题内容地址发生时间创建时间
${n.title }${n.description }${n.address }${n.newsTime?string("yyyy-MM-dd HH:mm") }${n.createTime?string("yyyy-MM-dd HH:mm") }
27 |
28 | 29 |
30 | 31 | 32 | 33 | <#assign formId = "newsPageForm"> 34 | <#assign showPageId = "ibox"> 35 | <#include "/common/page.ftl"/> 36 |
37 |
38 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/config/ThreadPoolConfig.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.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 | * @author 胡桃夹子 11 | * @date 2021-12-02 17:33 12 | */ 13 | @Configuration 14 | public class ThreadPoolConfig { 15 | 16 | 17 | @Bean("taskExecutor") 18 | public ThreadPoolTaskExecutor taskExecutor() { 19 | ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); 20 | //设置核心线程数 21 | taskExecutor.setCorePoolSize(2); 22 | //设置最大线程数 23 | taskExecutor.setMaxPoolSize(10); 24 | //设置线程空闲等待时间 25 | taskExecutor.setKeepAliveSeconds(3000); 26 | //设置任务等待队列的大小 27 | taskExecutor.setQueueCapacity(100); 28 | // 设置线程池内线程名称的前缀-------阿里编码规约推荐--方便出错后进行调试 29 | taskExecutor.setThreadNamePrefix("site-tool-task-"); 30 | //设置任务的拒绝策略,RejectedExecutionHandler类型的变量,表示线程池的饱和策略。 31 | //如果阻塞队列满了并且没有空闲的线程,这时如果继续提交任务,就需要采取一种策略处理该任务。线程池提供了4种策略: 32 | //AbortPolicy:直接抛出异常,这是默认策略; 33 | //CallerRunsPolicy:用调用者所在的线程来执行任务; 34 | //DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务; 35 | //DiscardPolicy:直接丢弃任务; 36 | taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 37 | //初始化 38 | taskExecutor.initialize(); 39 | return taskExecutor; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/model/auth/Role.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.model.auth; 2 | 3 | import cn.springboot.model.BaseEntity; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | 8 | 9 | /** 10 | * 角色对象 11 | * 12 | * @author 胡桃夹子 13 | * @date 2022/3/15 14:12 14 | */ 15 | @TableName("t_sys_role") 16 | public class Role implements BaseEntity { 17 | 18 | private static final long serialVersionUID = -6982490361440305761L; 19 | 20 | 21 | /** 22 | * 主键 23 | */ 24 | @TableId("id") 25 | private String id; 26 | 27 | /** 28 | * 角色名 29 | */ 30 | @TableField("name") 31 | private String name; 32 | 33 | /** 34 | * 角色编码 35 | */ 36 | @TableField("code") 37 | private String code; 38 | 39 | /** 40 | * 备注 41 | */ 42 | @TableField("remark") 43 | private String remark; 44 | 45 | @Override 46 | public String getId() { 47 | return id; 48 | } 49 | 50 | public void setId(String id) { 51 | this.id = id; 52 | } 53 | 54 | public String getName() { 55 | return name; 56 | } 57 | 58 | public void setName(String name) { 59 | this.name = name; 60 | } 61 | 62 | public String getCode() { 63 | return code; 64 | } 65 | 66 | public void setCode(String code) { 67 | this.code = code; 68 | } 69 | 70 | public String getRemark() { 71 | return remark; 72 | } 73 | 74 | public void setRemark(String remark) { 75 | this.remark = remark; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/generator/SqlServerTypeConvertCustom.java: -------------------------------------------------------------------------------- 1 | package org.mybatis.generator; 2 | 3 | import com.baomidou.mybatisplus.generator.config.GlobalConfig; 4 | import com.baomidou.mybatisplus.generator.config.ITypeConvert; 5 | import com.baomidou.mybatisplus.generator.config.converts.SqlServerTypeConvert; 6 | import com.baomidou.mybatisplus.generator.config.po.TableField; 7 | import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; 8 | import com.baomidou.mybatisplus.generator.config.rules.IColumnType; 9 | 10 | 11 | public class SqlServerTypeConvertCustom extends SqlServerTypeConvert implements ITypeConvert { 12 | @Override 13 | public IColumnType processTypeConvert(GlobalConfig globalConfig, String fieldType) { 14 | String t = fieldType.toLowerCase(); 15 | if (t.contains("tinyint")) { 16 | return DbColumnType.INTEGER; 17 | } else if (t.contains("decimal")) { 18 | return DbColumnType.BIG_DECIMAL; 19 | } else if (fieldType.toLowerCase().contains("datetime")) { 20 | return DbColumnType.DATE; 21 | } 22 | return super.processTypeConvert(globalConfig, fieldType); 23 | } 24 | 25 | /** 26 | * 执行类型转换 27 | * 28 | * @param globalConfig 全局配置 29 | * @param tableField 字段列信息 30 | * @return ignore 31 | */ 32 | @Override 33 | public IColumnType processTypeConvert(GlobalConfig globalConfig, TableField tableField) { 34 | String t = tableField.getName().toLowerCase(); 35 | if (t.contains("is_deleted")) { 36 | return DbColumnType.BOOLEAN; 37 | } 38 | return processTypeConvert(globalConfig, tableField.getType()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/locks/SharedLock.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.locks; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | /** 6 | *

共享锁接口

7 | * 8 | *

9 | * 指定共享锁池的大小,可以被不同线程共享的锁,获取锁的线程都可以访问共享资源。 10 | * 同一线程可以一次性获取多个锁,这些锁不能分开获取,锁池必须能够一次性提供。 11 | *

12 | * 13 | * @author 胡桃夹子 14 | */ 15 | public interface SharedLock extends DistributedLock { 16 | 17 | /** 18 | *

获取指定数量的锁

19 | * 20 | * @param qty 获取锁的数量,必须大于等于 1,并且不能大于共享锁的总数量 21 | *

22 | * 能一次性获取指定数量的锁才算成功获取
23 | * 若无法获取锁,则当前线程阻塞,直到成功获取锁为止 24 | */ 25 | void lock(int qty); 26 | 27 | /** 28 | *

尝试获取指定数量的锁

29 | * 30 | * @param qty 获取锁的数量,必须大于等于 1,并且不能大于共享锁的总数量 31 | * @return 成功获取锁立即返回 true,否则立即返回 false 32 | *

33 | * 能一次性获取指定数量的锁才算成功获取,也就是锁池空闲锁的数量必须能够满足。 34 | */ 35 | boolean tryLock(int qty); 36 | 37 | /** 38 | *

在限定时间内获取指定数量的锁

39 | * 40 | * @param qty 获取锁的数量,必须大于等于 1,并且不能大于共享锁的总数量 41 | * @param time 时间数值,长整形数字,必须大于等于 1 42 | * @param unit 时间单位 43 | * @return 成功获取锁返回 true,否则返回 false 44 | * @see TimeUnit 45 | *

46 | * 能一次性获取指定数量的锁才算成功获取,也就是锁池空闲锁的数量必须能够满足。 47 | */ 48 | boolean tryLock(int qty, long time, TimeUnit unit); 49 | 50 | /** 51 | *

释放指定数量的锁

52 | * 53 | * @param qty 释放锁的数量,必须大于等于 1,并且不能大于已经获取的锁的数量 54 | *

55 | * 必须和lock()、tryLock()成对使用,释放锁后共享锁池大小将会增加。 56 | */ 57 | void unlock(int qty); 58 | 59 | /** 60 | *

获取当前共享锁实例的锁数量

61 | * 62 | * @return 当前共享锁实例中所获取的锁数量 63 | */ 64 | int getInstanceLocks(); 65 | 66 | /** 67 | *

获取当前共享锁池中的锁数量

68 | * 69 | * @return 当前共享锁池中的锁数量,按锁名称进行统计,包含其它锁实例所获取的锁 70 | */ 71 | int getTotalLocks(); 72 | } 73 | -------------------------------------------------------------------------------- /src/main/resources/templates/view/news/edit_form.ftl: -------------------------------------------------------------------------------- 1 | 2 |
3 | 6 |
7 | 8 |
9 |
10 |
11 | 14 |
15 | 16 |
17 |
18 |
19 | 22 |
23 | 24 |
25 |
26 |
27 | 30 |
31 | 32 |
33 |
34 |
35 |
36 | 39 | 40 |
41 |
42 | -------------------------------------------------------------------------------- /src/main/resources/static/css/plugins/iCheck/green.css: -------------------------------------------------------------------------------- 1 | /* iCheck plugin Minimal skin, green 2 | ----------------------------------- */ 3 | .icheckbox_minimal-green, 4 | .iradio_minimal-green { 5 | display: inline-block; 6 | *display: inline; 7 | vertical-align: middle; 8 | margin: 0; 9 | padding: 0; 10 | width: 18px; 11 | height: 18px; 12 | background: url(minimal-green.png) no-repeat #fff; 13 | border: none; 14 | cursor: pointer; 15 | } 16 | 17 | .icheckbox_minimal-green { 18 | background-position: 0 0; 19 | } 20 | .icheckbox_minimal-green.hover { 21 | background-position: -20px 0; 22 | } 23 | .icheckbox_minimal-green.checked { 24 | background-position: -40px 0; 25 | } 26 | .icheckbox_minimal-green.disabled { 27 | background-position: -60px 0; 28 | cursor: default; 29 | } 30 | .icheckbox_minimal-green.checked.disabled { 31 | background-position: -80px 0; 32 | } 33 | 34 | .iradio_minimal-green { 35 | background-position: -100px 0; 36 | } 37 | .iradio_minimal-green.hover { 38 | background-position: -120px 0; 39 | } 40 | .iradio_minimal-green.checked { 41 | background-position: -140px 0; 42 | } 43 | .iradio_minimal-green.disabled { 44 | background-position: -160px 0; 45 | cursor: default; 46 | } 47 | .iradio_minimal-green.checked.disabled { 48 | background-position: -180px 0; 49 | } 50 | 51 | /* Retina support */ 52 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), 53 | only screen and (-moz-min-device-pixel-ratio: 1.5), 54 | only screen and (-o-min-device-pixel-ratio: 1.5), 55 | only screen and (min-device-pixel-ratio: 1.5) { 56 | .icheckbox_minimal-green, 57 | .iradio_minimal-green { 58 | background-image: url(minimal-green@2x.png); 59 | -webkit-background-size: 200px 20px; 60 | background-size: 200px 20px; 61 | } 62 | } -------------------------------------------------------------------------------- /src/main/java/cn/springboot/Application.java: -------------------------------------------------------------------------------- 1 | package cn.springboot; 2 | 3 | 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.boot.web.server.ConfigurableWebServerFactory; 9 | import org.springframework.boot.web.server.ErrorPage; 10 | import org.springframework.boot.web.server.WebServerFactoryCustomizer; 11 | import org.springframework.boot.web.servlet.ServletComponentScan; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.http.HttpStatus; 14 | 15 | /** 16 | * start application 17 | * 18 | * @author 胡桃夹子 19 | * @date 2022/3/15 13:21 20 | */ 21 | @ServletComponentScan(basePackages = "cn.springboot") 22 | @SpringBootApplication(scanBasePackages = "cn.springboot") 23 | public class Application { 24 | 25 | private static final Logger LOG = LoggerFactory.getLogger(Application.class); 26 | 27 | public static void main(String[] args) { 28 | SpringApplication springApplication = new SpringApplication(Application.class); 29 | springApplication.run(args); 30 | LOG.info(">>>>>>>>>>>>>>>>>>>> start successful <<<<<<<<<<<<<<<<<<<<"); 31 | } 32 | 33 | @Bean 34 | public WebServerFactoryCustomizer webServerFactoryCustomizer() { 35 | return factory -> { 36 | ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/error/401.html"); 37 | ErrorPage error403Page = new ErrorPage(HttpStatus.FORBIDDEN, "/error/403.html"); 38 | ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404.html"); 39 | ErrorPage error405Page = new ErrorPage(HttpStatus.METHOD_NOT_ALLOWED, "/error/405.html"); 40 | ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500.html"); 41 | factory.addErrorPages(error401Page, error403Page, error404Page, error405Page, error500Page); 42 | }; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/resources/templates/view/news/list_page.ftl: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <#if page?? && page.records?? && (page.records?size > 0) > 16 | <#list page.records as n> 17 | 18 | 19 | 20 | 21 | 22 | 23 | 31 | 32 | 33 | 34 | 35 |
标题内容地址发生时间创建时间操作
${n.title }${n.description }${n.address }${n.newsTime?string("yyyy-MM-dd HH:mm") }${n.createTime?string("yyyy-MM-dd HH:mm") } 24 |
25 | 29 |
30 |
36 |
37 | 38 | 39 |
40 | 41 | 42 | 43 | <#assign formId = "newsPageForm"> 44 | <#assign showPageId = "ibox"> 45 | <#include "/common/page.ftl"/> 46 |
47 | 48 |
49 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/web/controller/MainController.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.web.controller; 2 | 3 | import org.apache.commons.collections.MapUtils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.ModelMap; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | 12 | import javax.servlet.http.HttpServletRequest; 13 | import java.util.Map; 14 | 15 | @Controller 16 | public class MainController { 17 | 18 | private static final Logger log = LoggerFactory.getLogger(MainController.class); 19 | 20 | @GetMapping(value = {"/", "index"}) 21 | public String home() { 22 | log.info("# 进入默认首页"); 23 | return "index"; 24 | } 25 | 26 | @GetMapping("leftnav") 27 | public String leftnav() { 28 | return "leftnav"; 29 | } 30 | 31 | @GetMapping("topnav") 32 | public String topnav() { 33 | return "topnav"; 34 | } 35 | 36 | @RequestMapping(value = "/error", method = {RequestMethod.POST, RequestMethod.GET}) 37 | String error(HttpServletRequest request, ModelMap map) { 38 | Object err = request.getAttribute("err"); 39 | if (err != null) { 40 | log.error("# err={}", err); 41 | map.put("err", err); 42 | } 43 | Object pageUrl = request.getAttribute("pageUrl"); 44 | if (pageUrl != null) { 45 | log.error("# pageUrl={}", pageUrl); 46 | map.put("pageUrl", pageUrl); 47 | } 48 | Map param = request.getParameterMap(); 49 | if (MapUtils.isNotEmpty(param)) { 50 | for (Map.Entry entry : param.entrySet()) { 51 | map.put(entry.getKey(), entry.getValue()); 52 | log.info("# error parameter.name=[{}],parameter.value=[{}]", entry.getKey(), entry.getValue()); 53 | } 54 | } 55 | log.info("# 进入错误页面"); 56 | return "common/error"; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/resources/templates/view/login/login.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 后台管理系统登录 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 50 | 54 | 55 | 56 | 57 | 58 |
59 | 67 |
68 | 69 | 70 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/pk/local/LocalIdGenerator.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.pk.local; 2 | 3 | /** 4 | * @author 胡桃夹子 5 | * @Description 获取全局唯一ID,根据Twitter雪花ID算法。为兼顾到前端失精问题,把返回值long改成String:
6 | * SnowFlake算法用来生成64位的ID,刚好可以用long整型存储,能够用于分布式系统中生产唯一的ID, 并且生成的ID有大致的顺序。 在这次实现中,生成的64位ID可以分成5个部分:
7 | * 0 - 41位时间戳 - 5位数据中心标识 - 5位机器标识 - 12位序列号
8 | * @date Dec 20, 2017 10:26:59 PM 9 | */ 10 | public interface LocalIdGenerator { 11 | 12 | /** 13 | * 获取全局唯一ID 14 | * 15 | * @param dataCenterId 数据中心标识ID 16 | * @param machineId 机器标识ID 17 | * @return 全局唯一ID 18 | */ 19 | String nextUniqueId(long dataCenterId, long machineId); 20 | 21 | /** 22 | * 获取全局唯一ID 23 | * 24 | * @param startTimestamp 起始计算时间戳(默认2017-01-01) 25 | * @param dataCenterId 数据中心标识ID 26 | * @param machineId 机器标识ID 27 | * @return 全局唯一ID 28 | */ 29 | String nextUniqueId(String startTimestamp, long dataCenterId, long machineId) throws Exception; 30 | 31 | /** 32 | * 获取全局唯一ID 33 | * 34 | * @param startTimestamp 起始计算时间戳(默认2017-01-01) 35 | * @param dataCenterId 数据中心标识ID 36 | * @param machineId 机器标识ID 37 | * @return 全局唯一ID 38 | */ 39 | String nextUniqueId(long startTimestamp, long dataCenterId, long machineId); 40 | 41 | /** 42 | * 批量获取全局唯一ID 43 | * 44 | * @param dataCenterId 数据中心标识ID 45 | * @param machineId 机器标识ID 46 | * @param count 批量获取数量 47 | * @return 全局唯一ID 48 | */ 49 | String[] nextUniqueIds(long dataCenterId, long machineId, int count); 50 | 51 | /** 52 | * 批量获取全局唯一ID 53 | * 54 | * @param startTimestamp 起始计算时间戳(默认2017-01-01) 55 | * @param dataCenterId 数据中心标识ID 56 | * @param machineId 机器标识ID 57 | * @param count 批量获取数量 58 | * @return 全局唯一ID 59 | * @throws Exception 60 | */ 61 | String[] nextUniqueIds(String startTimestamp, long dataCenterId, long machineId, int count) throws Exception; 62 | 63 | /** 64 | * 批量获取全局唯一ID 65 | * 66 | * @param startTimestamp 起始计算时间戳(默认2017-01-01) 67 | * @param dataCenterId 数据中心标识ID 68 | * @param machineId 机器标识ID 69 | * @param count 批量获取数量 70 | * @return 全局唯一ID 71 | */ 72 | String[] nextUniqueIds(long startTimestamp, long dataCenterId, long machineId, int count); 73 | } 74 | -------------------------------------------------------------------------------- /src/main/resources/mapper/auth/UserRoleMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | insert into t_sys_user_role(id, user_id, role_id) 14 | values (#{id}, #{userId}, #{roleId}) 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 40 | 41 | 49 | 50 | 55 | 56 | 57 | update t_sys_user_role 58 | 59 | 60 | user_id = #{userId}, 61 | 62 | 63 | role_id = #{roleId} 64 | 65 | 66 | where id = #{id} 67 | 68 | 69 | 70 | delete 71 | from t_sys_user_role 72 | where id in #{id} 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/util/UUIDUtil.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.util; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | import java.util.UUID; 6 | 7 | 8 | /** 9 | * @author 胡桃夹子 10 | * @Description UUID工具类 11 | * @date Mar 16, 2017 5:19:54 PM 12 | */ 13 | public class UUIDUtil { 14 | 15 | /* 16 | * 知识点: 17 | * UUID(Universally Unique Identifier)全局唯一标识符,是指在一台机器上生成的数字; 18 | * 它保证对在同一时空中的所有机器都是唯一的。 19 | * 按照开放软件基金会(OSF)制定的标准计算, 20 | * 用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。 21 | * 由以下几部分的组合:当前日期和时间 22 | * (UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同), 23 | * 时钟序列,全局唯一的IEEE机器识别号(如果有网卡,从网卡获得,没有网卡以其他方式获得) 24 | * UUID的唯一缺陷在于生成的结果串会比较长。 25 | * 在Java中生成UUID主要有以下几种方式: 26 | * 1 JDK1.5 如果使用的JDK1.5的话,那么生成UUID变成了一件简单的事,因为JDK实现了UUID: 27 | * java.util.UUID,直接调用即可. 28 | * UUID uuid = UUID.randomUUID(); 29 | * String s = UUID.randomUUID().toString();用来生成数据库的主键id非常不错。 30 | * UUID是由一个十六位的数字组成,表现出来的形式例如 550E8400-E29B-11D4-A716-446655440000 31 | */ 32 | 33 | /*** 34 | * 随机产生32位16进制字符串 35 | * 36 | * @return 37 | */ 38 | public static String getRandom32PK() { 39 | return UUID.randomUUID().toString().replaceAll("-", ""); 40 | } 41 | 42 | /*** 43 | * 随机产生32位16进制字符串,以时间开头 44 | * 45 | * @return 46 | */ 47 | public static String getRandom32BeginTimePK() { 48 | Date d = new Date(); 49 | SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); 50 | String timeStr = sdf.format(d); 51 | String random32 = getRandom32PK(); 52 | return timeStr + random32.substring(17); 53 | } 54 | 55 | /*** 56 | * 随机产生32位16进制字符串,以时间结尾 57 | * 58 | * @return 59 | */ 60 | public static String getRandom32EndTimePK() { 61 | Date d = new Date(); 62 | SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); 63 | String timeStr = sdf.format(d); 64 | String random32 = getRandom32PK(); 65 | return random32.substring(0, random32.length() - 17) + timeStr; 66 | } 67 | 68 | public static void main(String[] args) { 69 | System.out.println("随机" + UUIDUtil.getRandom32PK().length() + "位:" + UUIDUtil.getRandom32PK()); 70 | System.out.println("随机" + UUIDUtil.getRandom32BeginTimePK().length() + "位以时间打头:" + UUIDUtil.getRandom32BeginTimePK()); 71 | System.out.println("随机" + UUIDUtil.getRandom32EndTimePK().length() + "位以时间结尾:" + UUIDUtil.getRandom32EndTimePK()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/model/simple/News.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.model.simple; 2 | 3 | import cn.springboot.model.BaseEntity; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | 8 | import java.util.Date; 9 | 10 | 11 | /** 12 | * 新闻对象 13 | * 14 | * @author 胡桃夹子 15 | * @date 2022/3/15 14:10 16 | */ 17 | @TableName("t_news") 18 | public class News implements BaseEntity { 19 | 20 | private static final long serialVersionUID = 3624947930970250778L; 21 | 22 | @TableId("id") 23 | private String id; 24 | 25 | /** 26 | * 新闻标题 27 | */ 28 | @TableField("title") 29 | private String title; 30 | 31 | /** 32 | * 新闻内容 33 | */ 34 | @TableField("description") 35 | private String description; 36 | 37 | /** 38 | * 新闻发生地址 39 | */ 40 | @TableField("address") 41 | private String address; 42 | 43 | /** 44 | * 新闻发生时间 45 | */ 46 | @TableField("news_time") 47 | private Date newsTime; 48 | 49 | /** 50 | * 新闻发布时间 51 | */ 52 | @TableField("create_time") 53 | private Date createTime; 54 | 55 | @Override 56 | public String getId() { 57 | return id; 58 | } 59 | 60 | public void setId(String id) { 61 | this.id = id; 62 | } 63 | 64 | public String getTitle() { 65 | return title; 66 | } 67 | 68 | public void setTitle(String title) { 69 | this.title = title; 70 | } 71 | 72 | public String getDescription() { 73 | return description; 74 | } 75 | 76 | public void setDescription(String description) { 77 | this.description = description; 78 | } 79 | 80 | public String getAddress() { 81 | return address; 82 | } 83 | 84 | public void setAddress(String address) { 85 | this.address = address; 86 | } 87 | 88 | public Date getNewsTime() { 89 | return newsTime; 90 | } 91 | 92 | public void setNewsTime(Date newsTime) { 93 | this.newsTime = newsTime; 94 | } 95 | 96 | public Date getCreateTime() { 97 | return createTime; 98 | } 99 | 100 | public void setCreateTime(Date createTime) { 101 | this.createTime = createTime; 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ./logs 8 | springboot-freemarker 9 | %d{yyyy/MM/dd HH:mm:ss,SSS} %-6r [%-6p] [%t] %C.%M-(%L) %msg%n 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/main/resources/static/css/plugins/multiselect/tree-multiselect.min.css: -------------------------------------------------------------------------------- 1 | /* jQuery Tree Multiselect v1.14.5 | (c) Patrick Tsai et al. | MIT Licensed */ 2 | div.tree-multiselect div.title:hover, div.tree-multiselect input[type=checkbox]:hover { 3 | cursor: pointer 4 | } 5 | 6 | div.tree-multiselect { 7 | border: 1px solid #ddd; 8 | display: table; 9 | height: inherit; 10 | width: 100% 11 | } 12 | 13 | div.tree-multiselect > div.selected, div.tree-multiselect > div.selections { 14 | vertical-align: top; 15 | float: left; 16 | padding: 1%; 17 | width: 50%; 18 | height: 550px; 19 | overflow-y: auto; 20 | 21 | } 22 | 23 | div.tree-multiselect > div.selections { 24 | border-left: solid 1px #D8D8D8; 25 | float: right; 26 | } 27 | 28 | div.tree-multiselect > div.selections.no-border { 29 | border-right: none 30 | } 31 | 32 | div.tree-multiselect div.section > div.item, div.tree-multiselect div.section > div.section { 33 | padding-left: 20px 34 | } 35 | 36 | div.tree-multiselect div.item, div.tree-multiselect div.title { 37 | margin-bottom: 2px 38 | } 39 | 40 | div.tree-multiselect div.title { 41 | background: #f5f5f5; 42 | color: #333; 43 | padding: 2px 44 | } 45 | 46 | div.tree-multiselect div.title > * { 47 | display: inline-block 48 | } 49 | 50 | div.tree-multiselect div.title > span.collapse-section { 51 | margin: 0 3px; 52 | width: 8px 53 | } 54 | 55 | div.tree-multiselect input[type=checkbox] { 56 | margin-right: 5px 57 | } 58 | 59 | div.tree-multiselect > div.selections div.item { 60 | margin-left: 16px; 61 | 62 | } 63 | 64 | div.tree-multiselect > div.selected > div.item { 65 | padding: 5px; 66 | border-radius: 2px; 67 | background: #f5f5f5 68 | } 69 | 70 | div.tree-multiselect > div.selected.ui-sortable > div.item:hover { 71 | cursor: move 72 | } 73 | 74 | div.tree-multiselect span.description{ 75 | padding: 0 5px; 76 | margin-right: 5px; 77 | background: #f8ac59; 78 | color: #fff; 79 | border-radius: 2px 80 | } 81 | 82 | div.tree-multiselect span.remove-selected { 83 | padding: 0 5px; 84 | margin-right: 8px; 85 | background: #23c6c8; 86 | color: #fff; 87 | border-radius: 2px 88 | } 89 | 90 | div.tree-multiselect span.remove-selected:hover { 91 | cursor: pointer 92 | } 93 | 94 | div.tree-multiselect span.description:hover { 95 | cursor: help 96 | } 97 | 98 | div.tree-multiselect div.temp-description-popup { 99 | background: #FAFAFA; 100 | padding: 5px; 101 | border: 2px solid #676767; 102 | border-radius: 3px 103 | } 104 | 105 | div.tree-multiselect span.section-name { 106 | float: right; 107 | font-style: italic 108 | } -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/datasource/PageInfo.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.datasource; 2 | 3 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 4 | 5 | /** 6 | * 自定义分页对象,扩展 navigatepageNums 7 | * 8 | * @author 胡桃夹子 9 | * @date 2022-03-15 16:19 10 | */ 11 | public class PageInfo extends Page { 12 | 13 | /* 14 | https://github.com/pagehelper/Mybatis-PageHelper/blob/master/src/main/java/com/github/pagehelper/Page.java 15 | 16 | this.startRow = this.pageNum > 0 ? (this.pageNum - 1) * this.pageSize : 0; 17 | this.endRow = this.startRow + this.pageSize * (this.pageNum > 0 ? 1 : 0); 18 | */ 19 | 20 | public long getPrePage() { 21 | if (this.getCurrent() > 1) { 22 | // 最后需要加1 23 | return this.getCurrent() - 1; 24 | } 25 | return 1; 26 | } 27 | 28 | public long getNextPage() { 29 | if (this.getCurrent() < this.getPages()) { 30 | // 最后需要加1 31 | return this.getCurrent() + 1; 32 | } 33 | return this.getPages(); 34 | } 35 | 36 | public long getStartRow() { 37 | if (this.getCurrent() > 0) { 38 | // 最后需要加1 39 | return (this.getCurrent() - 1) * this.getSize() + 1; 40 | } 41 | return 0; 42 | } 43 | 44 | public long getEndRow() { 45 | long startRow = getStartRow(); 46 | long endRow = startRow + this.getSize() * (this.getCurrent() > 0 ? 1 : 0); 47 | if (endRow > this.getTotal()) { 48 | endRow = this.getTotal(); 49 | } else { 50 | endRow--; 51 | } 52 | return endRow; 53 | } 54 | 55 | public int[] getNavigatepageNums() { 56 | //当前的页码 57 | long current = this.getCurrent(); 58 | //一共多少页 59 | long totalPage = this.getPages(); 60 | //需要展示的页码个数 61 | int navSize = 8; 62 | 63 | //算出current左边有多少个 64 | int before = navSize / 2; 65 | 66 | //起始的页码,防止了起始为负数 67 | int start = current - before < 1 ? 1 : (int) (current - before); 68 | //终止的页码 69 | int end = start + navSize - 1; 70 | //如果终止页码大于等于总页码 71 | if (end >= totalPage) { 72 | //总页码就是终止页码 73 | end = (int) totalPage; 74 | //因为终止页码变动,起始页码也需要变动 75 | start = end - navSize + 1; 76 | //如果总页码小于展示页码个数的话,起始页码可能是负数,将它变为1 77 | if (start < 1) { 78 | start = 1; 79 | } 80 | } 81 | 82 | int[] navs = new int[totalPage < navSize ? (int) totalPage : navSize]; 83 | for (int i = start, j = 0; i <= end; i++, j++) { 84 | navs[j] = i; 85 | } 86 | return navs; 87 | } 88 | 89 | public PageInfo(long current, long size) { 90 | super(current, size); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/resources/mapper/auth/RolePermissionMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | insert into t_sys_role_permission(id, role_id, permission_id) 14 | values (#{id}, #{roleId}, #{permissionId}) 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 40 | 41 | 46 | 47 | 55 | 56 | 61 | 62 | 63 | update t_sys_role_permission 64 | 65 | 66 | role_id = #{roleId}, 67 | 68 | 69 | permission_id = #{permissionId} 70 | 71 | 72 | where id = #{id} 73 | 74 | 75 | 76 | delete 77 | from t_sys_role_permission 78 | where id = #{id} 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/locks/DistributedLock.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.locks; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | /** 6 | *

分布式锁接口

7 | * 8 | *

9 | * 分布式锁是控制不同JVM下的多个线程对共享资源进行访问的工具。 10 | * 通常,锁提供了对共享资源的独占访问。 11 | * 一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。 12 | * 不过,某些锁可能允许对共享资源并发访问,如 {@link cn.springboot.framework.locks.ReadWriteLock ReadWriteLock} 的读取锁,共享锁 {@link cn.springboot.framework.locks.SharedLock SharedLock} 。 13 | *

14 | * 15 | * @author 胡桃夹子 16 | */ 17 | public interface DistributedLock { 18 | 19 | /** 20 | *

获取锁

21 | *

22 | * 若无法获取锁,则当前线程阻塞,直到成功获取锁为止。

23 | * 代码示例:
24 | *

 25 |      * lock.lock();
 26 |      * try {
 27 |      *   //操作共享资源的代码
 28 |      * }
 29 |      * finally {
 30 |      *   lock.unlock();
 31 |      * }
 32 |      * 
33 | * 34 | *

35 | * 异常处理 :任何错误都会导致抛出运行时异常 {@link cn.springboot.framework.locks.exception.LockException LockException} 36 | *

37 | */ 38 | void lock(); 39 | 40 | /** 41 | *

尝试获取空闲状态的锁

42 | *

43 | * 若存在可用的锁,则获取并立即返回 true,否则立即返回 false,线程不会阻塞。

44 | * 代码示例:
45 | *

 46 |      * if(lock.tryLock()) {
 47 |      *   try {
 48 |      *     //操作共享资源的代码
 49 |      *   }
 50 |      *   finally {
 51 |      *     lock.unlock();
 52 |      *   }
 53 |      * } else {
 54 |      *    //无法获取锁时的处理代码
 55 |      * }
 56 |      * 
57 | * 58 | *

59 | * 异常处理 :任何错误都会导致抛出运行时异常 {@link cn.springboot.framework.locks.exception.LockException LockException} 60 | *

61 | * 62 | * @return 成功获取锁返回 true,否则返回 false 63 | */ 64 | boolean tryLock(); 65 | 66 | /** 67 | *

在限定时间内获取空闲状态的锁

68 | *

69 | * 若存在可用的锁,则获取并立即返回 true,否则会在限定时间内不停尝试获取,直到成功获取返回 true 或者失败返回 false。

70 | * 代码示例:
71 | *

 72 |      * if(lock.tryLock(1, TimeUnit.SECONDS)) {
 73 |      *   try {
 74 |      *     //操作共享资源的代码
 75 |      *   }
 76 |      *   finally {
 77 |      *     lock.unlock();
 78 |      *   }
 79 |      * }else{
 80 |      *   //无法获取锁时的处理代码
 81 |      * }
 82 |      * 
83 | * 84 | *

85 | * 异常处理 :任何错误都会导致抛出运行时异常 {@link cn.springboot.framework.locks.exception.LockException LockException} 86 | *

87 | * 88 | * @param time 时间数值,长整形数字,必须大于等于 1 89 | * @param unit 时间单位 90 | * @return 成功获取锁返回 true,否则返回 false 91 | * @see TimeUnit 92 | */ 93 | boolean tryLock(long time, TimeUnit unit); 94 | 95 | /** 96 | *

释放锁

97 | *

98 | * 必须和lock(),tryLock()成对使用,由持有锁的对象释放锁。

99 | * 100 | *

101 | * 异常处理 :任何错误都会导致抛出运行时异常 {@link cn.springboot.framework.locks.exception.UnLockException UnLockException} 102 | *

103 | */ 104 | void unlock(); 105 | } 106 | -------------------------------------------------------------------------------- /src/main/resources/templates/common/page.ftl: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
当前显示 ${page.startRow } 到 ${page.endRow } 条,共 ${page.pages } 页 ${page.total } 条
5 |
6 |
7 | 42 |
43 | 44 |
45 |
46 | 64 | -------------------------------------------------------------------------------- /src/main/resources/mapper/auth/RoleMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 42 | 43 | 44 | 49 | 50 | 54 | 55 | 60 | 61 | 69 | 70 | 75 | 76 | 77 | update t_sys_role 78 | 79 | 80 | name = #{name}, 81 | 82 | 83 | code = #{code}, 84 | 85 | 86 | remark = #{remark} 87 | 88 | 89 | where id = #{id} 90 | 91 | 92 | 93 | delete 94 | from t_sys_role 95 | where id = #{id} 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/service/auth/impl/RoleServiceImpl.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.service.auth.impl; 2 | 3 | import cn.springboot.framework.exception.BusinessException; 4 | import cn.springboot.framework.pk.FactoryAboutKey; 5 | import cn.springboot.framework.pk.TableEnum; 6 | import cn.springboot.mapper.auth.PermissionMapper; 7 | import cn.springboot.mapper.auth.RoleMapper; 8 | import cn.springboot.mapper.auth.RolePermissionMapper; 9 | import cn.springboot.model.auth.Permission; 10 | import cn.springboot.model.auth.Role; 11 | import cn.springboot.model.auth.RolePermission; 12 | import cn.springboot.service.auth.RoleService; 13 | import org.apache.commons.lang3.StringUtils; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.stereotype.Service; 18 | 19 | import java.util.List; 20 | 21 | /** 22 | * 角色相关接口 23 | * 24 | * @author 胡桃夹子 25 | * @date 2022/3/15 14:17 26 | */ 27 | @Service 28 | public class RoleServiceImpl implements RoleService { 29 | 30 | private static final Logger LOG = LoggerFactory.getLogger(RoleServiceImpl.class); 31 | 32 | @Autowired 33 | private RoleMapper roleMapper; 34 | 35 | @Autowired 36 | private PermissionMapper permissionMapper; 37 | 38 | @Autowired 39 | private RolePermissionMapper rolePermissionMapper; 40 | 41 | @Override 42 | public void addRole(Role role) { 43 | if (role == null || StringUtils.isBlank(role.getName())) { 44 | return; 45 | } 46 | if (LOG.isDebugEnabled()) { 47 | LOG.debug("## 添加角色 : {}", role.getName()); 48 | } 49 | Role r = findRoleByCode(role.getCode()); 50 | if (r == null) { 51 | role.setId(FactoryAboutKey.getPK(TableEnum.T_SYS_ROLE)); 52 | roleMapper.insert(role); 53 | } 54 | } 55 | 56 | @Override 57 | public Role findRoleByCode(String code) { 58 | LOG.debug("## 根据编码查询角色 {}", code); 59 | return roleMapper.findRoleByCode(code); 60 | } 61 | 62 | @Override 63 | public List findRoleByUserId(String userId) { 64 | return roleMapper.findRoleByUserId(userId); 65 | } 66 | 67 | @Override 68 | public void addRolePermission(String roleCode, String permissionKey) { 69 | Role role = findRoleByCode(roleCode); 70 | if (role == null) { 71 | throw new BusinessException("role-fail", "## 给角色授权失败, 角色编码错误"); 72 | } 73 | Permission permis = permissionMapper.findPermissionByKey(permissionKey); 74 | if (permis == null) { 75 | throw new BusinessException("role-fail", "## 给角色授权失败, 菜单KEY不存在,key=" + permissionKey); 76 | } 77 | 78 | RolePermission rolePermission = new RolePermission(); 79 | rolePermission.setRoleId(role.getId()); 80 | rolePermission.setPermissionId(permis.getId()); 81 | 82 | RolePermission rp = rolePermissionMapper.findRolePermission(rolePermission); 83 | if (rp == null) { 84 | rolePermission.setId(FactoryAboutKey.getPK(TableEnum.T_SYS_ROLE_PERMISSION)); 85 | rolePermissionMapper.insert(rolePermission); 86 | } 87 | 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/resources/static/js/plugins/fileinput/fileinput_locale_zh.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * FileInput Chinese Translations 3 | * 4 | * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or 5 | * any HTML markup tags in the messages must not be converted or translated. 6 | * 7 | * @see http://github.com/kartik-v/bootstrap-fileinput 8 | * @author kangqf 9 | * 10 | * NOTE: this file must be saved in UTF-8 encoding. 11 | */ 12 | (function ($) { 13 | "use strict"; 14 | // 使用方法 $('#f_logo').fileinput('getphoto','请先上传图片'); 方法返回结果: true false 15 | $.fn.fileinput.Constructor.prototype.getphoto=function (msg) { 16 | var self = this, $el = self.$element; 17 | var uploadtype=!!self.ajaxRequests.length; 18 | !uploadtype && self._showUploadError(msg); 19 | self.$btnUpload.attr('disabled', false); 20 | return uploadtype 21 | }; 22 | 23 | $.fn.fileinputLocales['zh'] = { 24 | fileSingle: '文件', 25 | filePlural: '多个文件', 26 | browseLabel: '选择 …', 27 | removeLabel: '移除', 28 | removeTitle: '清除选中文件', 29 | cancelLabel: '取消', 30 | cancelTitle: '取消进行中的上传', 31 | uploadLabel: '上传', 32 | uploadTitle: '上传选中文件', 33 | msgZoomTitle: '查看详情', 34 | msgZoomModalHeading: '详细预览', 35 | msgSizeTooLarge: '文件 "{name}" ({size} KB) 超过了允许大小 {maxSize} KB.', 36 | msgFilesTooLess: '你必须最少选择 {n} {files} 来上传. ', 37 | msgFilesTooMany: '选择的上传文件个数 ({n}) 超出最大文件的限制个数 {m}.', 38 | msgFileNotFound: '文件 "{name}" 未找到!', 39 | msgFileSecured: '安全限制,为了防止读取文件 "{name}".', 40 | msgFileNotReadable: '文件 "{name}" 不可读.', 41 | msgFilePreviewAborted: '取消 "{name}" 的预览.', 42 | msgFilePreviewError: '读取 "{name}" 时出现了一个错误.', 43 | msgInvalidFileType: '不正确的类型 "{name}". 只支持 "{types}" 类型的文件.', 44 | msgInvalidFileExtension: '不正确的文件扩展名 "{name}". 只支持 "{extensions}" 的文件扩展名.', 45 | msgUploadAborted: '该文件上传被中止', 46 | msgValidationError: '文件上传错误', 47 | msgLoading: '加载第 {index} 文件 共 {files} …', 48 | msgProgress: '加载第 {index} 文件 共 {files} - {name} - {percent}% 完成.', 49 | msgSelected: '{n} {files} 选中', 50 | msgFoldersNotAllowed: '只支持拖拽文件! 跳过 {n} 拖拽的文件夹.', 51 | msgImageWidthSmall: '图像文件"{name}"的宽度必须是至少{size}像素.', 52 | msgImageHeightSmall: '图像文件"{name}"的高度必须至少为{size}像素.', 53 | msgImageWidthLarge: '图像文件"{name}"的宽度不能超过{size}像素.', 54 | msgImageHeightLarge: '图像文件"{name}"的高度不能超过{size}像素.', 55 | msgImageResizeError: '无法获取的图像尺寸调整。', 56 | msgImageResizeException: '错误而调整图像大小。
{errors}
', 57 | dropZoneTitle: '拖拽文件到这里 …', 58 | slugCallback: function(text) { 59 | return text ? text.split(/(\\|\/)/g).pop().replace(/[^\w\u4e00-\u9fa5\-.\\\/ ]+/g, '') : ''; 60 | }, 61 | fileActionSettings: { 62 | removeTitle: '删除文件', 63 | uploadTitle: '上传文件', 64 | indicatorNewTitle: '没有上传', 65 | indicatorSuccessTitle: '上传', 66 | indicatorErrorTitle: '上传错误', 67 | indicatorLoadingTitle: '上传 ...' 68 | } 69 | }; 70 | })(window.jQuery); -------------------------------------------------------------------------------- /src/main/java/cn/springboot/model/auth/Permission.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.model.auth; 2 | 3 | import cn.springboot.model.BaseEntity; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | 8 | /** 9 | * 菜单对象 10 | * 11 | * @author 胡桃夹子 12 | * @date 2022/3/15 14:13 13 | */ 14 | @TableName("t_sys_permission") 15 | public class Permission implements BaseEntity { 16 | 17 | private static final long serialVersionUID = -7141829387338999544L; 18 | 19 | /** 20 | * 主键 21 | */ 22 | @TableId("id") 23 | private String id; 24 | 25 | /** 26 | * 菜单名 27 | */ 28 | @TableField("name") 29 | private String name; 30 | 31 | /** 32 | * 菜单样式图标名 33 | */ 34 | @TableField("css_class") 35 | private String cssClass; 36 | 37 | /** 38 | * 菜单相对URL 39 | */ 40 | @TableField("url") 41 | private String url; 42 | 43 | /** 44 | * 菜单KEY 45 | */ 46 | @TableField("skey") 47 | private String skey; 48 | 49 | /** 50 | * 父菜单KEY 51 | */ 52 | @TableField("parent_key") 53 | private String parentKey; 54 | 55 | /** 56 | * 是否显示:1=有效,0=无效 57 | */ 58 | @TableField("hide") 59 | private Integer hide; 60 | 61 | /** 62 | * 菜单级别,最多只能有3级 63 | */ 64 | @TableField("lev") 65 | private Integer lev; 66 | 67 | /** 68 | * 菜单排序 69 | */ 70 | @TableField("sort") 71 | private Integer sort; 72 | 73 | @Override 74 | public String getId() { 75 | return id; 76 | } 77 | 78 | public void setId(String id) { 79 | this.id = id; 80 | } 81 | 82 | public String getName() { 83 | return name; 84 | } 85 | 86 | public void setName(String name) { 87 | this.name = name; 88 | } 89 | 90 | public String getCssClass() { 91 | return cssClass; 92 | } 93 | 94 | public void setCssClass(String cssClass) { 95 | this.cssClass = cssClass; 96 | } 97 | 98 | public String getSkey() { 99 | return skey; 100 | } 101 | 102 | public void setSkey(String skey) { 103 | this.skey = skey; 104 | } 105 | 106 | public Integer getHide() { 107 | return hide; 108 | } 109 | 110 | public void setHide(Integer hide) { 111 | this.hide = hide; 112 | } 113 | 114 | public Integer getLev() { 115 | return lev; 116 | } 117 | 118 | public void setLev(Integer lev) { 119 | this.lev = lev; 120 | } 121 | 122 | public String getUrl() { 123 | return url; 124 | } 125 | 126 | public void setUrl(String url) { 127 | this.url = url; 128 | } 129 | 130 | public Integer getSort() { 131 | return sort; 132 | } 133 | 134 | public void setSort(Integer sort) { 135 | this.sort = sort; 136 | } 137 | 138 | public String getParentKey() { 139 | return parentKey; 140 | } 141 | 142 | public void setParentKey(String parentKey) { 143 | this.parentKey = parentKey; 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/main/resources/static/js/plugins/metisMenu/jquery.metisMenu.js: -------------------------------------------------------------------------------- 1 | /* 2 | * metismenu - v2.0.2 3 | * A jQuery menu plugin 4 | * https://github.com/onokumus/metisMenu 5 | * 6 | * Made by Osman Nuri Okumus 7 | * Under MIT License 8 | */ 9 | 10 | !function(a){"use strict";function b(){var a=document.createElement("mm"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}function c(b){return this.each(function(){var c=a(this),d=c.data("mm"),f=a.extend({},e.DEFAULTS,c.data(),"object"==typeof b&&b);d||c.data("mm",d=new e(this,f)),"string"==typeof b&&d[b]()})}a.fn.emulateTransitionEnd=function(b){var c=!1,e=this;a(this).one("mmTransitionEnd",function(){c=!0});var f=function(){c||a(e).trigger(d.end)};return setTimeout(f,b),this};var d=b();d&&(a.event.special.mmTransitionEnd={bindType:d.end,delegateType:d.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}});var e=function(b,c){this.$element=a(b),this.options=a.extend({},e.DEFAULTS,c),this.transitioning=null,this.init()};e.TRANSITION_DURATION=350,e.DEFAULTS={toggle:!0,doubleTapToGo:!1,activeClass:"active"},e.prototype.init=function(){var b=this,c=this.options.activeClass;this.$element.find("li."+c).has("ul").children("ul").addClass("collapse in"),this.$element.find("li").not("."+c).has("ul").children("ul").addClass("collapse"),this.options.doubleTapToGo&&this.$element.find("li."+c).has("ul").children("a").addClass("doubleTapToGo"),this.$element.find("li").has("ul").children("a").on("click.metisMenu",function(d){var e=a(this),f=e.parent("li"),g=f.children("ul");return d.preventDefault(),f.hasClass(c)?b.hide(g):b.show(g),b.options.doubleTapToGo&&b.doubleTapToGo(e)&&"#"!==e.attr("href")&&""!==e.attr("href")?(d.stopPropagation(),void(document.location=e.attr("href"))):void 0})},e.prototype.doubleTapToGo=function(a){var b=this.$element;return a.hasClass("doubleTapToGo")?(a.removeClass("doubleTapToGo"),!0):a.parent().children("ul").length?(b.find(".doubleTapToGo").removeClass("doubleTapToGo"),a.addClass("doubleTapToGo"),!1):void 0},e.prototype.show=function(b){var c=this.options.activeClass,f=a(b),g=f.parent("li");if(!this.transitioning&&!f.hasClass("in")){g.addClass(c),this.options.toggle&&this.hide(g.siblings().children("ul.in")),f.removeClass("collapse").addClass("collapsing").height(0),this.transitioning=1;var h=function(){f.removeClass("collapsing").addClass("collapse in").height(""),this.transitioning=0};return d?void f.one("mmTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(e.TRANSITION_DURATION).height(f[0].scrollHeight):h.call(this)}},e.prototype.hide=function(b){var c=this.options.activeClass,f=a(b);if(!this.transitioning&&f.hasClass("in")){f.parent("li").removeClass(c),f.height(f.height())[0].offsetHeight,f.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var g=function(){this.transitioning=0,f.removeClass("collapsing").addClass("collapse")};return d?void f.height(0).one("mmTransitionEnd",a.proxy(g,this)).emulateTransitionEnd(e.TRANSITION_DURATION):g.call(this)}};var f=a.fn.metisMenu;a.fn.metisMenu=c,a.fn.metisMenu.Constructor=e,a.fn.metisMenu.noConflict=function(){return a.fn.metisMenu=f,this}}(jQuery); -------------------------------------------------------------------------------- /src/main/resources/static/js/plugins/validate/validate-cn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by zifan on 2015/10/16. 3 | */ 4 | $.validator.setDefaults({ 5 | ignore: ":hidden", 6 | success: function (span) { 7 | if(span.prev().length!=0){ 8 | if (span.prev().is(":radio") || span.prev().is(":checkbox") || $(span).prev().prop("tagName").toLowerCase() == "select") { 9 | $(span).addClass("hide") 10 | } 11 | else { 12 | $(span).addClass("success fa fa-check"); 13 | } 14 | } 15 | }, 16 | highlight: function (element, errorClass) { 17 | $(element).addClass("error"); 18 | $(element).parent().find("." + errorClass).removeClass("hide success fa fa-check"); 19 | } 20 | }); 21 | 22 | jQuery.extend(jQuery.validator.messages, { 23 | required: "此字段不能为空,请按要求输入内容!", 24 | remote: "格式错误,请重新输入", 25 | email: "请输入正确格式的电子邮件", 26 | url: "请输入合法的网址", 27 | date: "请输入合法的日期", 28 | dateISO: "请输入合法的日期 (ISO).", 29 | number: "请输入合法的数字", 30 | digits: "只能输入整数", 31 | creditcard: "请输入合法的信用卡号", 32 | equalTo: "请再次输入相同的值", 33 | accept: "请输入拥有合法后缀名的字符串", 34 | maxlength: jQuery.validator.format("大侠饶命!我要被撑爆了,输入的内容不能大于 {0} !"), 35 | minlength: jQuery.validator.format("小懒懒!要多动动手哦,最少要输入 {0} 位!"), 36 | rangelength: jQuery.validator.format("请输入 一个长度介于 {0} 和 {1} 之间的内容"), 37 | range: jQuery.validator.format("请输入一个介于 {0} 和 {1} 之间的值"), 38 | max: jQuery.validator.format("请输入一个最大为{0} 的值"), 39 | min: jQuery.validator.format("请输入一个最小为{0} 的值") 40 | }); 41 | 42 | // 只能输入[0-9]数字  43 | jQuery.validator.addMethod("isDigits", function (value, element) { 44 | return this.optional(element) || /^\d+$/.test(value); 45 | }, "只能输入0-9数字"); 46 | // 判断中文字符   47 | jQuery.validator.addMethod("isChinese", function (value, element) { 48 | return this.optional(element) || /^[\u0391-\uFFE5]+$/.test(value); 49 | }, "只能包含中文字符。"); 50 | // 判断英文字符   51 | jQuery.validator.addMethod("isEnglish", function (value, element) { 52 | return this.optional(element) || /^[A-Za-z]+$/.test(value); 53 | }, "只能包含英文字符。"); 54 | // 手机号码验证      55 | jQuery.validator.addMethod("isMobile", function (value, element) { 56 | var length = value.length; 57 | return this.optional(element) || (length == 11 && /^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1}))+\d{8})$/.test(value)); 58 | }, "请正确填写您的手机号码。"); 59 | // 电话号码验证      60 | jQuery.validator.addMethod("isPhone", function (value, element) { 61 | var tel = /^(\d{3,4}-?)?\d{7,9}$/g; 62 | return this.optional(element) || (tel.test(value)); 63 | }, "请正确填写您的电话号码。"); 64 | // 联系电话(手机/电话皆可)验证     65 | jQuery.validator.addMethod("isTel", function (value, element) { 66 | var length = value.length; 67 | var mobile = /^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1}))+\d{8})$/; 68 | var tel = /^(\d{3,4}-?)?\d{7,9}$/g; 69 | return this.optional(element) || tel.test(value) || (length == 11 && mobile.test(value)); 70 | }, "请正确填写您的联系方式"); 71 | 72 | //公共方法 表单项的 选择对应的 隐藏区域 73 | $("form").find('*[data-toggle-name]').on('change', function () { 74 | var val = $(this).find(":selected").attr("data-box-name"); 75 | var $target = $(this).attr('data-toggle-name'); 76 | $($target + " #" + val).removeClass().addClass("show").siblings().removeClass().addClass("hide"); 77 | //$target.find("[data-box]").toggle(); 78 | }); 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/main/resources/static/css/plugins/gritter/jquery.gritter.css: -------------------------------------------------------------------------------- 1 | /* the norm */ 2 | #gritter-notice-wrapper { 3 | position:fixed; 4 | top:40px; 5 | right:20px; 6 | width:301px; 7 | z-index:9999; 8 | 9 | -webkit-animation-duration: 1s; 10 | animation-duration: 1s; 11 | -webkit-animation-fill-mode: both; 12 | animation-fill-mode: both; 13 | 14 | -webkit-animation-name: bounceIn; 15 | animation-name: bounceIn; 16 | } 17 | @keyframes bounceIn { 18 | 0% { 19 | opacity: 0; 20 | -webkit-transform: scale(.3); 21 | -ms-transform: scale(.3); 22 | transform: scale(.3); 23 | } 24 | 25 | 50% { 26 | opacity: 1; 27 | -webkit-transform: scale(1.05); 28 | -ms-transform: scale(1.05); 29 | transform: scale(1.05); 30 | } 31 | 32 | 70% { 33 | -webkit-transform: scale(.9); 34 | -ms-transform: scale(.9); 35 | transform: scale(.9); 36 | } 37 | 38 | 100% { 39 | opacity: 1; 40 | -webkit-transform: scale(1); 41 | -ms-transform: scale(1); 42 | transform: scale(1); 43 | } 44 | } 45 | #gritter-notice-wrapper.top-left { 46 | left: 20px; 47 | right: auto; 48 | } 49 | #gritter-notice-wrapper.bottom-right { 50 | top: auto; 51 | left: auto; 52 | bottom: 20px; 53 | right: 20px; 54 | } 55 | #gritter-notice-wrapper.bottom-left { 56 | top: auto; 57 | right: auto; 58 | bottom: 20px; 59 | left: 20px; 60 | } 61 | .gritter-item-wrapper { 62 | position:relative; 63 | margin:0 0 10px 0; 64 | background:url('images/ie-spacer.gif'); /* ie7/8 fix */ 65 | } 66 | 67 | .hover .gritter-top { 68 | /*background-position:right -30px;*/ 69 | } 70 | .gritter-bottom { 71 | height:8px; 72 | margin:0; 73 | } 74 | 75 | .gritter-item { 76 | display:block; 77 | background-color: rgba(39,58,75,0.8); 78 | border-radius: 4px; 79 | color:#eee; 80 | padding:10px 11px 10px 11px; 81 | font-size: 11px; 82 | } 83 | .hover .gritter-item { 84 | background-position:right -40px; 85 | } 86 | .gritter-item p { 87 | padding:0; 88 | margin:0; 89 | word-wrap:break-word; 90 | } 91 | 92 | .gritter-item a:hover { 93 | color: #f8ac59; 94 | text-decoration: underline; 95 | } 96 | .gritter-close { 97 | display:none; 98 | position:absolute; 99 | top:5px; 100 | right:3px; 101 | background:url(images/gritter.png) no-repeat left top; 102 | cursor:pointer; 103 | width:30px; 104 | height:30px; 105 | text-indent:-9999em; 106 | } 107 | .gritter-title { 108 | font-size:12px; 109 | font-weight:bold; 110 | padding:0 0 7px 0; 111 | display:block; 112 | text-transform: uppercase; 113 | } 114 | .gritter-image { 115 | width:48px; 116 | height:48px; 117 | float:left; 118 | } 119 | .gritter-with-image, 120 | .gritter-without-image { 121 | padding:0; 122 | } 123 | .gritter-with-image { 124 | width:220px; 125 | float:right; 126 | } 127 | /* for the light (white) version of the gritter notice */ 128 | .gritter-light .gritter-item, 129 | .gritter-light .gritter-bottom, 130 | .gritter-light .gritter-top, 131 | .gritter-light .gritter-close { 132 | background-image: url(images/gritter-light.png); 133 | color: #222; 134 | } 135 | .gritter-light .gritter-title { 136 | text-shadow: none; 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/config/authority/filter/SQLInjectionFilterServlet.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.config.authority.filter; 2 | 3 | import cn.springboot.config.authority.service.xss.XSSSecurityConstants; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import javax.servlet.Filter; 8 | import javax.servlet.FilterChain; 9 | import javax.servlet.FilterConfig; 10 | import javax.servlet.ServletException; 11 | import javax.servlet.ServletRequest; 12 | import javax.servlet.ServletResponse; 13 | import javax.servlet.annotation.WebFilter; 14 | import javax.servlet.annotation.WebInitParam; 15 | import javax.servlet.http.HttpServletRequest; 16 | import java.io.IOException; 17 | import java.util.Map; 18 | 19 | /** 20 | * @author 胡桃夹子 21 | * @Description 防止SQL注入 22 | * @date Mar 24, 2017 7:42:29 PM 23 | */ 24 | @WebFilter(urlPatterns = "/*", filterName = "SQLInjection", initParams = {@WebInitParam(name = "regularExpression", value = "(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|" + "(\\b(select|update|and|or|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|into|drop|execute)\\b)")}) 25 | public class SQLInjectionFilterServlet implements Filter { 26 | 27 | private static final Logger log = LoggerFactory.getLogger(SQLInjectionFilterServlet.class); 28 | 29 | private String regularExpression; 30 | 31 | public SQLInjectionFilterServlet() { 32 | log.debug("# init "); 33 | } 34 | 35 | @Override 36 | public void init(FilterConfig filterConfig) throws ServletException { 37 | regularExpression = filterConfig.getInitParameter("regularExpression"); 38 | log.info("######### regularExpression={}", regularExpression); 39 | } 40 | 41 | /** 42 | * 如果需要输入“'”、“;”、“--”这些字符 可以考虑将这些字符转义为html转义字符 “'”转义字符为:' “;”转义字符为:; 43 | * “--”转义字符为:-- 44 | */ 45 | @SuppressWarnings("rawtypes") 46 | @Override 47 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 48 | HttpServletRequest req = (HttpServletRequest) request; 49 | String requestUrl = req.getRequestURL().toString(); 50 | String contextPath = req.getContextPath(); 51 | // 获取剥离contextPath的url路径 52 | requestUrl = requestUrl.substring(requestUrl.indexOf(contextPath) + contextPath.length()); 53 | 54 | Map parametersMap = request.getParameterMap(); 55 | for (Object o : parametersMap.entrySet()) { 56 | Map.Entry entry = (Map.Entry) o; 57 | String[] value = (String[]) entry.getValue(); 58 | for (String msg : value) { 59 | if (null != msg && msg.matches(regularExpression)) { 60 | log.info("#疑似SQL注入攻击!参数名称:{};录入信息:{}", entry.getKey(), msg); 61 | request.setAttribute("err", "您输入的参数有非法字符,请输入正确的参数!"); 62 | request.setAttribute("pageUrl", req.getRequestURI()); 63 | request.getRequestDispatcher(request.getServletContext().getContextPath() + XSSSecurityConstants.FILTER_ERROR_PAGE).forward(request, response); 64 | return; 65 | } 66 | } 67 | } 68 | chain.doFilter(request, response); 69 | } 70 | 71 | @Override 72 | public void destroy() { 73 | log.debug("# destroy "); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/resources/mapper/simple/NewsMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | insert into t_news(id, address, create_time, description, news_time, title) 17 | values (#{id}, #{address}, #{createTime}, #{description}, #{newsTime}, #{title}) 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 45 | 46 | 56 | 57 | 68 | 69 | 70 | update t_news 71 | 72 | 73 | address = #{address}, 74 | 75 | 76 | create_time = #{createTime}, 77 | 78 | 79 | description = #{description}, 80 | 81 | 82 | news_time = #{newsTime}, 83 | 84 | 85 | title = #{title}, 86 | 87 | 88 | where id = #{id} 89 | 90 | 91 | 92 | delete 93 | from t_news 94 | where id = #{id} 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/util/salt/Digests.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.util.salt; 2 | 3 | import org.apache.commons.lang3.Validate; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.security.GeneralSecurityException; 8 | import java.security.MessageDigest; 9 | import java.security.SecureRandom; 10 | 11 | /** 12 | * @author 胡桃夹子 13 | * @Description 支持SHA-1/MD5消息摘要的工具类
14 | * 返回ByteSource,可进一步被编码为Hex, Base64或UrlSafeBase64 15 | * @date Apr 12, 2017 9:36:27 AM 16 | */ 17 | public class Digests { 18 | 19 | private static final String SHA1 = "SHA-1"; 20 | private static final String MD5 = "MD5"; 21 | 22 | private static final SecureRandom random = new SecureRandom(); 23 | 24 | /** 25 | * 对输入字符串进行sha1散列. 26 | */ 27 | public static byte[] sha1(byte[] input) { 28 | return digest(input, SHA1, null, 1); 29 | } 30 | 31 | public static byte[] sha1(byte[] input, byte[] salt) { 32 | return digest(input, SHA1, salt, 1); 33 | } 34 | 35 | public static byte[] sha1(byte[] input, byte[] salt, int iterations) { 36 | return digest(input, SHA1, salt, iterations); 37 | } 38 | 39 | /** 40 | * 对字符串进行散列, 支持md5与sha1算法. 41 | */ 42 | private static byte[] digest(byte[] input, String algorithm, byte[] salt, int iterations) { 43 | try { 44 | MessageDigest digest = MessageDigest.getInstance(algorithm); 45 | 46 | if (salt != null) { 47 | digest.update(salt); 48 | } 49 | 50 | byte[] result = digest.digest(input); 51 | 52 | for (int i = 1; i < iterations; i++) { 53 | digest.reset(); 54 | result = digest.digest(result); 55 | } 56 | return result; 57 | } catch (GeneralSecurityException e) { 58 | throw Exceptions.unchecked(e); 59 | } 60 | } 61 | 62 | /** 63 | * 生成随机的Byte[]作为salt. 64 | * 65 | * @param numBytes byte数组的大小 66 | */ 67 | public static byte[] generateSalt(int numBytes) { 68 | Validate.isTrue(numBytes > 0, "numBytes argument must be a positive integer (1 or larger)", numBytes); 69 | 70 | byte[] bytes = new byte[numBytes]; 71 | random.nextBytes(bytes); 72 | return bytes; 73 | } 74 | 75 | /** 76 | * 对文件进行md5散列. 77 | */ 78 | public static byte[] md5(InputStream input) throws IOException { 79 | return digest(input, MD5); 80 | } 81 | 82 | /** 83 | * 对文件进行sha1散列. 84 | */ 85 | public static byte[] sha1(InputStream input) throws IOException { 86 | return digest(input, SHA1); 87 | } 88 | 89 | private static byte[] digest(InputStream input, String algorithm) throws IOException { 90 | try { 91 | MessageDigest messageDigest = MessageDigest.getInstance(algorithm); 92 | int bufferLength = 8 * 1024; 93 | byte[] buffer = new byte[bufferLength]; 94 | int read = input.read(buffer, 0, bufferLength); 95 | 96 | while (read > -1) { 97 | messageDigest.update(buffer, 0, read); 98 | read = input.read(buffer, 0, bufferLength); 99 | } 100 | 101 | return messageDigest.digest(); 102 | } catch (GeneralSecurityException e) { 103 | throw Exceptions.unchecked(e); 104 | } 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/resources/conf/xss_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | true 7 | 8 | true 9 | 10 | true 11 | 12 | true 13 | 14 | 15 | true 16 | 17 | 18 | 19 | 26 | 27 | imgshow 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | .*.*]]> 63 | 64 | .*.*]]> 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | .*[[.&[^a]]|[|a|\n|\r\n|\r|\u0085|\u2028|\u2029]]*]]> 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/locks/zookeeper/ZookeeperMutexLock.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.locks.zookeeper; 2 | 3 | import cn.springboot.framework.locks.MutexLock; 4 | import cn.springboot.framework.locks.exception.BuildLockException; 5 | import cn.springboot.framework.locks.exception.LockException; 6 | import cn.springboot.framework.locks.exception.UnLockException; 7 | import org.apache.curator.framework.CuratorFramework; 8 | import org.apache.curator.framework.recipes.locks.InterProcessLock; 9 | import org.apache.curator.framework.recipes.locks.InterProcessMutex; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.concurrent.TimeUnit; 14 | 15 | /** 16 | *

ZooKeeper互斥锁实现

17 | * 18 | *

基于 ZooKeeper 实现了分布式互斥锁接口{@link MutexLock MutexLock}

19 | * 20 | * @author 胡桃夹子 21 | */ 22 | public class ZookeeperMutexLock implements MutexLock { 23 | 24 | private static final Logger LOG = LoggerFactory.getLogger(ZookeeperMutexLock.class); 25 | 26 | private final InterProcessLock lock; 27 | 28 | private int holdCount = 0; 29 | 30 | protected ZookeeperMutexLock(CuratorFramework client, String name) { 31 | if (client == null || name == null) { 32 | throw new BuildLockException("init ZookeeperMutexLock error!"); 33 | } 34 | if (!name.startsWith("/")) { 35 | name = "/" + name; 36 | } 37 | lock = new InterProcessMutex(client, name); 38 | } 39 | 40 | @Override 41 | public void lock() { 42 | try { 43 | lock.acquire(); 44 | holdCount++; 45 | } catch (Exception e) { 46 | LOG.error(e.getMessage(), e); 47 | throw new LockException(e.getMessage(), e); 48 | } 49 | } 50 | 51 | @Override 52 | public boolean tryLock() { 53 | try { 54 | boolean result = lock.acquire(1, TimeUnit.MICROSECONDS); 55 | if (result) { 56 | holdCount++; 57 | } 58 | return result; 59 | } catch (Exception e) { 60 | LOG.error(e.getMessage(), e); 61 | throw new LockException(e.getMessage(), e); 62 | } 63 | } 64 | 65 | @Override 66 | public boolean tryLock(long time, TimeUnit unit) { 67 | try { 68 | if (time <= 0 || unit == null) { 69 | throw new LockException("lock time error!"); 70 | } 71 | boolean result = lock.acquire(time, unit); 72 | if (result) { 73 | holdCount++; 74 | } 75 | LOG.debug("{}", result); 76 | return result; 77 | /* 78 | time=unit.toMillis(time); 79 | log.debug("time:{}",time); 80 | long beginTime=System.currentTimeMillis(); 81 | boolean result=false; 82 | do{ 83 | result=lock.acquire(1, TimeUnit.MICROSECONDS); 84 | log.debug("diff:{}",System.currentTimeMillis()-beginTime); 85 | }while(System.currentTimeMillis()-beginTime < time); 86 | return result; 87 | */ 88 | } catch (Exception e) { 89 | LOG.error(e.getMessage(), e); 90 | throw new LockException(e.getMessage(), e); 91 | } 92 | } 93 | 94 | @Override 95 | public void unlock() { 96 | try { 97 | lock.release(); 98 | holdCount--; 99 | } catch (Exception e) { 100 | LOG.error(e.getMessage(), e); 101 | throw new UnLockException(e.getMessage(), e); 102 | } 103 | } 104 | 105 | @Override 106 | public int getHoldCount() { 107 | return holdCount; 108 | } 109 | 110 | @Override 111 | public boolean isHeldByCurrentThread() { 112 | return holdCount > 0; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/test/java/cn/springboot/InitServiceTest.java: -------------------------------------------------------------------------------- 1 | package cn.springboot; 2 | 3 | import cn.springboot.model.auth.Permission; 4 | import cn.springboot.model.auth.Role; 5 | import cn.springboot.model.auth.User; 6 | import cn.springboot.service.auth.PermissionService; 7 | import cn.springboot.service.auth.RoleService; 8 | import cn.springboot.service.auth.UserService; 9 | import cn.springboot.web.shiro.vo.PermissionEnumUtil; 10 | import cn.springboot.web.shiro.vo.RoleEnumUtil; 11 | import org.junit.jupiter.api.Test; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | 15 | import java.util.List; 16 | 17 | @SpringBootTest 18 | public class InitServiceTest { 19 | 20 | @Autowired 21 | private UserService userService; 22 | 23 | @Autowired 24 | private RoleService roleService; 25 | 26 | @Autowired 27 | private PermissionService permissionService; 28 | 29 | /** 30 | * 创建角色 31 | */ 32 | private void addRoles() { 33 | try { 34 | roleService.addRole(RoleEnumUtil.ADMIN_ROLE.getRole()); 35 | roleService.addRole(RoleEnumUtil.COMMON_ROLE.getRole()); 36 | } catch (Exception e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | 41 | /** 42 | * 创建菜单 43 | */ 44 | private void addPermission() { 45 | try { 46 | List permis = PermissionEnumUtil.getPermissions(); 47 | for (Permission permission : permis) { 48 | permissionService.addPermission(permission); 49 | } 50 | } catch (Exception e) { 51 | e.printStackTrace(); 52 | } 53 | } 54 | 55 | /** 56 | * 创建用户 57 | */ 58 | private void addUsers() { 59 | try { 60 | Role adminRole = roleService.findRoleByCode(RoleEnumUtil.ADMIN_ROLE.getRoleCode()); 61 | Role commonRole = roleService.findRoleByCode(RoleEnumUtil.COMMON_ROLE.getRoleCode()); 62 | 63 | String password = "123456"; 64 | 65 | User admin = new User(); 66 | admin.setUsername("admin"); 67 | admin.setEmail("infowangxin@163.com"); 68 | admin.setTrueName("管理员"); 69 | admin.setPassword(password); 70 | admin.setOrganizeId(adminRole.getId()); 71 | userService.addUser(admin, adminRole); 72 | 73 | User nutcracker = new User(); 74 | nutcracker.setUsername("nutcracker"); 75 | nutcracker.setTrueName("胡桃夹子"); 76 | nutcracker.setEmail("infowangxin@139.com"); 77 | nutcracker.setPassword(password); 78 | nutcracker.setOrganizeId(commonRole.getId()); 79 | userService.addUser(nutcracker, commonRole); 80 | } catch (Exception e) { 81 | e.printStackTrace(); 82 | } 83 | } 84 | 85 | // 给角色授权 86 | private void bindRolePermission() { 87 | try { 88 | Role adminRole = roleService.findRoleByCode(RoleEnumUtil.ADMIN_ROLE.getRoleCode()); 89 | Role commonRole = roleService.findRoleByCode(RoleEnumUtil.COMMON_ROLE.getRoleCode()); 90 | 91 | List permissionList = PermissionEnumUtil.getPermissions(); 92 | for (Permission permission : permissionList) { 93 | roleService.addRolePermission(adminRole.getCode(), permission.getSkey()); 94 | roleService.addRolePermission(commonRole.getCode(), permission.getSkey()); 95 | } 96 | } catch (Exception e) { 97 | e.printStackTrace(); 98 | } 99 | } 100 | 101 | @Test 102 | public void importTestData() { 103 | try { 104 | addRoles(); 105 | addPermission(); 106 | addUsers(); 107 | bindRolePermission(); 108 | } catch (Exception e) { 109 | e.printStackTrace(); 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/model/auth/User.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.model.auth; 2 | 3 | import cn.springboot.model.BaseEntity; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * 用户对象 12 | * 13 | * @author 胡桃夹子 14 | * @date 2022/3/15 14:13 15 | */ 16 | @TableName("t_sys_user") 17 | public class User implements BaseEntity { 18 | 19 | private static final long serialVersionUID = 1227495748732942139L; 20 | 21 | /** 22 | * 主键 23 | */ 24 | @TableId("id") 25 | private String id; 26 | 27 | /** 28 | * 登录账号 29 | */ 30 | @TableField("username") 31 | private String username; 32 | 33 | /** 34 | * 登录密码 35 | */ 36 | @TableField("password") 37 | private String password; 38 | 39 | /** 40 | * salt 41 | */ 42 | @TableField("salt") 43 | private String salt; 44 | 45 | /** 46 | * 真实姓名 47 | */ 48 | @TableField("true_name") 49 | private String trueName; 50 | 51 | /** 52 | * 邮箱 53 | */ 54 | @TableField("email") 55 | private String email; 56 | 57 | /** 58 | * 部门ID 59 | */ 60 | @TableField("organize_id") 61 | private String organizeId; 62 | 63 | /** 64 | * 状态:0=有效,1=无效 65 | */ 66 | @TableField("status") 67 | private Integer status; 68 | 69 | /** 70 | * 上将登录时间 71 | */ 72 | @TableField("last_login_time") 73 | private Date lastLoginTime; 74 | 75 | /** 76 | * 创建时间 77 | */ 78 | @TableField("create_time") 79 | private Date createTime; 80 | 81 | /** 82 | * 更新时间 83 | */ 84 | @TableField("modify_time") 85 | private Date modifyTime; 86 | 87 | @Override 88 | public String getId() { 89 | return id; 90 | } 91 | 92 | public void setId(String id) { 93 | this.id = id; 94 | } 95 | 96 | public String getEmail() { 97 | return email; 98 | } 99 | 100 | public void setEmail(String email) { 101 | this.email = email; 102 | } 103 | 104 | public String getUsername() { 105 | return username; 106 | } 107 | 108 | public void setUsername(String username) { 109 | this.username = username; 110 | } 111 | 112 | public String getTrueName() { 113 | return trueName; 114 | } 115 | 116 | public void setTrueName(String trueName) { 117 | this.trueName = trueName; 118 | } 119 | 120 | public String getPassword() { 121 | return password; 122 | } 123 | 124 | public void setPassword(String password) { 125 | this.password = password; 126 | } 127 | 128 | public String getSalt() { 129 | return salt; 130 | } 131 | 132 | public void setSalt(String salt) { 133 | this.salt = salt; 134 | } 135 | 136 | public Integer getStatus() { 137 | return status; 138 | } 139 | 140 | public void setStatus(Integer status) { 141 | this.status = status; 142 | } 143 | 144 | public String getOrganizeId() { 145 | return organizeId; 146 | } 147 | 148 | public void setOrganizeId(String organizeID) { 149 | this.organizeId = organizeID; 150 | } 151 | 152 | public Date getCreateTime() { 153 | return createTime; 154 | } 155 | 156 | public void setCreateTime(Date createTime) { 157 | this.createTime = createTime; 158 | } 159 | 160 | public Date getLastLoginTime() { 161 | return lastLoginTime; 162 | } 163 | 164 | public void setLastLoginTime(Date lastLoginTime) { 165 | this.lastLoginTime = lastLoginTime; 166 | } 167 | 168 | public Date getModifyTime() { 169 | return modifyTime; 170 | } 171 | 172 | public void setModifyTime(Date modifyTime) { 173 | this.modifyTime = modifyTime; 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/main/resources/templates/topnav.ftl: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/service/simple/impl/NewsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.service.simple.impl; 2 | 3 | import cn.springboot.framework.constant.Constants; 4 | import cn.springboot.framework.datasource.DataSourceTagger; 5 | import cn.springboot.framework.datasource.PageInfo; 6 | import cn.springboot.framework.exception.BusinessException; 7 | import cn.springboot.framework.pk.FactoryAboutKey; 8 | import cn.springboot.framework.pk.TableEnum; 9 | import cn.springboot.mapper.simple.NewsMapper; 10 | import cn.springboot.model.simple.News; 11 | import cn.springboot.service.simple.NewsService; 12 | import com.baomidou.dynamic.datasource.annotation.DS; 13 | import org.apache.commons.lang3.StringUtils; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.stereotype.Service; 18 | import org.springframework.transaction.annotation.Transactional; 19 | 20 | import java.util.Calendar; 21 | import java.util.List; 22 | 23 | /** 24 | * @author 胡桃夹子 25 | * @Description 新闻接口实现类 26 | * @date Mar 16, 2017 5:19:24 PM 27 | */ 28 | @Service 29 | public class NewsServiceImpl implements NewsService { 30 | 31 | private static final Logger log = LoggerFactory.getLogger(NewsServiceImpl.class); 32 | 33 | @Autowired 34 | private NewsMapper newsMapper; 35 | 36 | //@DS(DataSourceTagger.DB2) 37 | @Transactional(rollbackFor = BusinessException.class) 38 | @Override 39 | public boolean addNews(News news) { 40 | if (news != null) { 41 | news.setId(FactoryAboutKey.getPK(TableEnum.T_NEWS)); 42 | news.setCreateTime(Calendar.getInstance().getTime()); 43 | int flag = newsMapper.insert(news); 44 | if (StringUtils.equals(news.getTitle(), "a")) { 45 | throw new BusinessException("001", "测试事务回溯"); 46 | } 47 | return flag == 1; 48 | } else { 49 | return false; 50 | } 51 | } 52 | 53 | @Override 54 | public boolean editNews(News news) { 55 | if (news != null && StringUtils.isNotBlank(news.getId())) { 56 | int flag = newsMapper.updateById(news); 57 | return flag == 1; 58 | } else { 59 | return false; 60 | } 61 | } 62 | 63 | @Override 64 | public News findNewsById(String id) { 65 | if (StringUtils.isBlank(id)) { 66 | return null; 67 | } else { 68 | return newsMapper.selectById(id); 69 | } 70 | } 71 | 72 | @Override 73 | public List findNewsByKeywords(String keywords) { 74 | //默认数据库 75 | log.info("# 查询默认数据库"); 76 | return newsMapper.findNewsByKeywords(keywords); 77 | } 78 | 79 | @DS(DataSourceTagger.DB1) 80 | @Override 81 | public PageInfo findNewsByPage1(Integer pageNum, String keywords) { 82 | // 数据库1 83 | PageInfo resultPage = findByPage(pageNum, keywords); 84 | log.info("# 数据库1 resultPage={}", resultPage); 85 | return resultPage; 86 | } 87 | 88 | 89 | @DS(DataSourceTagger.DB2) 90 | @Override 91 | public PageInfo findNewsByPage2(Integer pageNum, String keywords) { 92 | // 数据库2 93 | PageInfo resultPage = findByPage(pageNum, keywords); 94 | log.info("# 数据库2 resultPage={}", resultPage); 95 | return resultPage; 96 | } 97 | 98 | @Override 99 | public PageInfo findNewsByPage(Integer pageNum, String keywords) { 100 | PageInfo resultPage = findByPage(pageNum, keywords); 101 | log.info("# 默认数据库 resultPage={}", resultPage); 102 | return resultPage; 103 | } 104 | 105 | private PageInfo findByPage(Integer pageNum, String keywords) { 106 | log.debug("# pageNum={},keywords={}", pageNum, keywords); 107 | if (pageNum == null) { 108 | pageNum = 1; 109 | } 110 | PageInfo page = new PageInfo<>(pageNum, Constants.PAGE_SIZE); 111 | return newsMapper.findNewsByPage(page, keywords); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/web/shiro/MShiroFilterFactoryBean.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.web.shiro; 2 | 3 | import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 4 | import org.apache.shiro.web.filter.mgt.FilterChainManager; 5 | import org.apache.shiro.web.filter.mgt.FilterChainResolver; 6 | import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver; 7 | import org.apache.shiro.web.mgt.WebSecurityManager; 8 | import org.apache.shiro.web.servlet.AbstractShiroFilter; 9 | import org.springframework.beans.factory.BeanInitializationException; 10 | 11 | import javax.servlet.FilterChain; 12 | import javax.servlet.ServletException; 13 | import javax.servlet.ServletRequest; 14 | import javax.servlet.ServletResponse; 15 | import javax.servlet.http.HttpServletRequest; 16 | import java.io.IOException; 17 | import java.util.HashSet; 18 | import java.util.Set; 19 | 20 | /** 21 | * @author 胡桃夹子 22 | * @Description 继承 ShiroFilterFactoryBean 处理拦截资源文件问题 23 | * @date Apr 12, 2017 10:32:28 AM 24 | */ 25 | public class MShiroFilterFactoryBean extends ShiroFilterFactoryBean { 26 | 27 | /** 28 | * 对ShiroFilter来说,需要直接忽略的请求 29 | */ 30 | private final Set ignoreExt; 31 | 32 | public MShiroFilterFactoryBean() { 33 | super(); 34 | ignoreExt = new HashSet<>(); 35 | ignoreExt.add(".jpg"); 36 | ignoreExt.add(".png"); 37 | ignoreExt.add(".gif"); 38 | ignoreExt.add(".bmp"); 39 | ignoreExt.add(".js"); 40 | ignoreExt.add(".css"); 41 | } 42 | 43 | @Override 44 | protected AbstractShiroFilter createInstance() throws Exception { 45 | 46 | org.apache.shiro.mgt.SecurityManager securityManager = getSecurityManager(); 47 | if (securityManager == null) { 48 | String msg = "SecurityManager property must be set."; 49 | throw new BeanInitializationException(msg); 50 | } 51 | 52 | if (!(securityManager instanceof WebSecurityManager)) { 53 | String msg = "The security manager does not implement the WebSecurityManager interface."; 54 | throw new BeanInitializationException(msg); 55 | } 56 | 57 | FilterChainManager manager = createFilterChainManager(); 58 | 59 | PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver(); 60 | chainResolver.setFilterChainManager(manager); 61 | 62 | return new MSpringShiroFilter((WebSecurityManager) securityManager, chainResolver); 63 | } 64 | 65 | private final class MSpringShiroFilter extends AbstractShiroFilter { 66 | 67 | private MSpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) { 68 | super(); 69 | if (webSecurityManager == null) { 70 | throw new IllegalArgumentException("WebSecurityManager property cannot be null."); 71 | } 72 | setSecurityManager(webSecurityManager); 73 | if (resolver != null) { 74 | setFilterChainResolver(resolver); 75 | } 76 | } 77 | 78 | @Override 79 | protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws ServletException, IOException { 80 | HttpServletRequest request = (HttpServletRequest) servletRequest; 81 | String str = request.getRequestURI().toLowerCase(); 82 | // 因为ShiroFilter 拦截所有请求(在上面我们配置了urlPattern 为 * ,当然你也可以在那里精确的添加要处理的路径,这样就不需要这个类了),而在每次请求里面都做了session的读取和更新访问时间等操作,这样在集群部署session共享的情况下,数量级的加大了处理量负载。 83 | // 所以我们这里将一些能忽略的请求忽略掉。 84 | // 当然如果你的集群系统使用了动静分离处理,静态资料的请求不会到Filter这个层面,便可以忽略。 85 | boolean flag = true; 86 | int idx = 0; 87 | if ((idx = str.indexOf(".")) > 0) { 88 | str = str.substring(idx); 89 | if (ignoreExt.contains(str.toLowerCase())) { 90 | flag = false; 91 | } 92 | } 93 | if (flag) { 94 | super.doFilterInternal(servletRequest, servletResponse, chain); 95 | } else { 96 | chain.doFilter(servletRequest, servletResponse); 97 | } 98 | } 99 | 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/framework/pk/local/impl/LocalIdGeneratorImpl.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.framework.pk.local.impl; 2 | 3 | import cn.springboot.framework.pk.local.LocalIdGenerator; 4 | import org.apache.commons.lang3.ArrayUtils; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.text.SimpleDateFormat; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | @Component 14 | public class LocalIdGeneratorImpl implements LocalIdGenerator { 15 | private static final Logger logger = LoggerFactory.getLogger(LocalIdGeneratorImpl.class); 16 | 17 | private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss:SSS"; 18 | /** 19 | * 2017-01-01 00:00:00:000 20 | */ 21 | private static final long DEFAULT_START_TIMESTAMP = 1483200000000L; 22 | private static final SimpleDateFormat SIMPLE_DATE_FORMAT; 23 | 24 | private final ConcurrentHashMap idGeneratorMap = new ConcurrentHashMap(); 25 | 26 | @Value("${frequentLogPrint:false}") 27 | private boolean frequentLogPrint; 28 | 29 | static { 30 | // format 31 | SIMPLE_DATE_FORMAT = new SimpleDateFormat(DATE_FORMAT); 32 | } 33 | 34 | public static String convert(String[] arrays) { 35 | if (ArrayUtils.isEmpty(arrays)) { 36 | return null; 37 | } 38 | 39 | StringBuilder builder = new StringBuilder(); 40 | for (String array : arrays) { 41 | builder.append(array).append(","); 42 | } 43 | 44 | String result = builder.toString(); 45 | result = result.substring(0, result.length() - 1); 46 | 47 | return result; 48 | } 49 | 50 | @Override 51 | public String nextUniqueId(long dataCenterId, long machineId) { 52 | return nextUniqueId(DEFAULT_START_TIMESTAMP, dataCenterId, machineId); 53 | } 54 | 55 | @Override 56 | public String nextUniqueId(String startTimestamp, long dataCenterId, long machineId) throws Exception { 57 | return nextUniqueId(SIMPLE_DATE_FORMAT.parse(startTimestamp).getTime(), dataCenterId, machineId); 58 | } 59 | 60 | @Override 61 | public String nextUniqueId(long startTimestamp, long dataCenterId, long machineId) { 62 | String nextUniqueId = getIdGenerator(startTimestamp, dataCenterId, machineId).nextId(); 63 | if (frequentLogPrint) { 64 | logger.info("Next unique id is {} for startTimestamp={}, dataCenterId={}, machineId={}", nextUniqueId, startTimestamp, dataCenterId, machineId); 65 | } 66 | return nextUniqueId; 67 | } 68 | 69 | @Override 70 | public String[] nextUniqueIds(long dataCenterId, long machineId, int count) { 71 | return nextUniqueIds(DEFAULT_START_TIMESTAMP, dataCenterId, machineId, count); 72 | } 73 | 74 | @Override 75 | public String[] nextUniqueIds(String startTimestamp, long dataCenterId, long machineId, int count) throws Exception { 76 | return nextUniqueIds(SIMPLE_DATE_FORMAT.parse(startTimestamp).getTime(), dataCenterId, machineId, count); 77 | } 78 | 79 | @Override 80 | public String[] nextUniqueIds(long startTimestamp, long dataCenterId, long machineId, int count) { 81 | String[] nextUniqueIds = getIdGenerator(startTimestamp, dataCenterId, machineId).nextIds(count); 82 | if (frequentLogPrint) { 83 | logger.info("Next unique ids is {} for startTimestamp={}, dataCenterId={}, machineId={}, count={}", convert(nextUniqueIds), startTimestamp, dataCenterId, machineId, count); 84 | } 85 | return nextUniqueIds; 86 | } 87 | 88 | private SnowflakeIdGenerator getIdGenerator(long startTimestamp, long dataCenterId, long machineId) { 89 | String key = dataCenterId + "-" + machineId; 90 | SnowflakeIdGenerator idGenerator = idGeneratorMap.get(key); 91 | if (idGenerator == null) { 92 | SnowflakeIdGenerator newIdGnerator = new SnowflakeIdGenerator(startTimestamp, dataCenterId, machineId); 93 | idGenerator = idGeneratorMap.putIfAbsent(key, newIdGnerator); 94 | if (idGenerator == null) { 95 | idGenerator = newIdGnerator; 96 | } 97 | } 98 | return idGenerator; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/cn/springboot/web/controller/LoginController.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.web.controller; 2 | 3 | import cn.springboot.model.auth.User; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.apache.shiro.SecurityUtils; 6 | import org.apache.shiro.authc.AuthenticationException; 7 | import org.apache.shiro.authc.ExcessiveAttemptsException; 8 | import org.apache.shiro.authc.IncorrectCredentialsException; 9 | import org.apache.shiro.authc.LockedAccountException; 10 | import org.apache.shiro.authc.UnknownAccountException; 11 | import org.apache.shiro.authc.UsernamePasswordToken; 12 | import org.apache.shiro.subject.Subject; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.stereotype.Controller; 16 | import org.springframework.ui.Model; 17 | import org.springframework.web.bind.annotation.GetMapping; 18 | import org.springframework.web.bind.annotation.ModelAttribute; 19 | import org.springframework.web.bind.annotation.PostMapping; 20 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 21 | 22 | /** 23 | * 认证相关接口 24 | * 25 | * @author 胡桃夹子 26 | * @date 2022/3/15 11:07 27 | */ 28 | @Controller 29 | public class LoginController { 30 | 31 | private static final Logger LOG = LoggerFactory.getLogger(LoginController.class); 32 | 33 | @GetMapping("login") 34 | String login(Model model) { 35 | model.addAttribute("user", new User()); 36 | LOG.info("#去登录"); 37 | return "view/login/login"; 38 | } 39 | 40 | @PostMapping("/login") 41 | public String login(@ModelAttribute("userForm") User user, RedirectAttributes redirectAttributes) { 42 | LOG.info("# 登录中 "); 43 | if (null == user || StringUtils.isBlank(user.getUsername()) || StringUtils.isBlank(user.getPassword())) { 44 | LOG.error("# 账号或密码错误"); 45 | return "login"; 46 | } 47 | 48 | String username = user.getUsername(); 49 | UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword()); 50 | // 获取当前的Subject 51 | Subject currentUser = SecurityUtils.getSubject(); 52 | try { 53 | // 在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查 54 | // 每个Realm都能在必要时对提交的AuthenticationTokens作出反应 55 | // 所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法 56 | LOG.info("对用户[{}]进行登录验证..验证开始", username); 57 | currentUser.login(token); 58 | LOG.info("对用户[{}]进行登录验证..验证通过", username); 59 | } catch (UnknownAccountException uae) { 60 | LOG.error("对用户[{}]进行登录验证..验证未通过,未知账户", username); 61 | setRedirectAttributes(redirectAttributes, "未知账户"); 62 | } catch (IncorrectCredentialsException ice) { 63 | LOG.error("对用户[{}]进行登录验证..验证未通过,错误的凭证", username); 64 | setRedirectAttributes(redirectAttributes, "密码不正确"); 65 | } catch (LockedAccountException lae) { 66 | LOG.error("对用户[{}]进行登录验证..验证未通过,账户已锁定", username); 67 | setRedirectAttributes(redirectAttributes, "账户已锁定"); 68 | } catch (ExcessiveAttemptsException eae) { 69 | LOG.error("对用户[{}]进行登录验证..验证未通过,错误次数过多", username); 70 | setRedirectAttributes(redirectAttributes, "用户名或密码错误次数过多"); 71 | } catch (AuthenticationException ae) { 72 | // 通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景 73 | LOG.error("对用户[{}]进行登录验证..验证未通过,堆栈轨迹如下", username); 74 | setRedirectAttributes(redirectAttributes, "用户名或密码不正确"); 75 | } 76 | // 验证是否登录成功 77 | if (currentUser.isAuthenticated()) { 78 | LOG.info("用户[{}]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)", username); 79 | return "redirect:/index"; 80 | } else { 81 | token.clear(); 82 | return "redirect:/login"; 83 | } 84 | } 85 | 86 | private void setRedirectAttributes(RedirectAttributes redirectAttributes, String message) { 87 | redirectAttributes.addFlashAttribute("message", message); 88 | } 89 | 90 | @GetMapping("/logout") 91 | public String logout() { 92 | SecurityUtils.getSubject().logout(); 93 | return "view/login/login"; 94 | } 95 | 96 | @GetMapping("/403") 97 | public String unauthorizedRole() { 98 | LOG.info("------没有权限-------"); 99 | return "403"; 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/resources/static/js/plugins/toastr/toastr.min.js: -------------------------------------------------------------------------------- 1 | !function(e){e(["jquery"],function(e){return function(){function t(e,t,n){return f({type:O.error,iconClass:g().iconClasses.error,message:e,optionsOverride:n,title:t})}function n(t,n){return t||(t=g()),v=e("#"+t.containerId),v.length?v:(n&&(v=c(t)),v)}function i(e,t,n){return f({type:O.info,iconClass:g().iconClasses.info,message:e,optionsOverride:n,title:t})}function o(e){w=e}function s(e,t,n){return f({type:O.success,iconClass:g().iconClasses.success,message:e,optionsOverride:n,title:t})}function a(e,t,n){return f({type:O.warning,iconClass:g().iconClasses.warning,message:e,optionsOverride:n,title:t})}function r(e){var t=g();v||n(t),l(e,t)||u(t)}function d(t){var i=g();return v||n(i),t&&0===e(":focus",t).length?void h(t):void(v.children().length&&v.remove())}function u(t){for(var n=v.children(),i=n.length-1;i>=0;i--)l(e(n[i]),t)}function l(t,n){return t&&0===e(":focus",t).length?(t[n.hideMethod]({duration:n.hideDuration,easing:n.hideEasing,complete:function(){h(t)}}),!0):!1}function c(t){return v=e("
").attr("id",t.containerId).addClass(t.positionClass).attr("aria-live","polite").attr("role","alert"),v.appendTo(e(t.target)),v}function p(){return{tapToDismiss:!0,toastClass:"toast",containerId:"toast-container",debug:!1,showMethod:"fadeIn",showDuration:300,showEasing:"swing",onShown:void 0,hideMethod:"fadeOut",hideDuration:1e3,hideEasing:"swing",onHidden:void 0,extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},iconClass:"toast-info",positionClass:"toast-top-right",timeOut:5e3,titleClass:"toast-title",messageClass:"toast-message",target:"body",closeHtml:'',newestOnTop:!0,preventDuplicates:!1,progressBar:!1}}function m(e){w&&w(e)}function f(t){function i(t){return!e(":focus",l).length||t?(clearTimeout(O.intervalId),l[r.hideMethod]({duration:r.hideDuration,easing:r.hideEasing,complete:function(){h(l),r.onHidden&&"hidden"!==b.state&&r.onHidden(),b.state="hidden",b.endTime=new Date,m(b)}})):void 0}function o(){(r.timeOut>0||r.extendedTimeOut>0)&&(u=setTimeout(i,r.extendedTimeOut),O.maxHideTime=parseFloat(r.extendedTimeOut),O.hideEta=(new Date).getTime()+O.maxHideTime)}function s(){clearTimeout(u),O.hideEta=0,l.stop(!0,!0)[r.showMethod]({duration:r.showDuration,easing:r.showEasing})}function a(){var e=(O.hideEta-(new Date).getTime())/O.maxHideTime*100;f.width(e+"%")}var r=g(),d=t.iconClass||r.iconClass;if("undefined"!=typeof t.optionsOverride&&(r=e.extend(r,t.optionsOverride),d=t.optionsOverride.iconClass||d),r.preventDuplicates){if(t.message===C)return;C=t.message}T++,v=n(r,!0);var u=null,l=e("
"),c=e("
"),p=e("
"),f=e("
"),w=e(r.closeHtml),O={intervalId:null,hideEta:null,maxHideTime:null},b={toastId:T,state:"visible",startTime:new Date,options:r,map:t};return t.iconClass&&l.addClass(r.toastClass).addClass(d),t.title&&(c.append(t.title).addClass(r.titleClass),l.append(c)),t.message&&(p.append(t.message).addClass(r.messageClass),l.append(p)),r.closeButton&&(w.addClass("toast-close-button").attr("role","button"),l.prepend(w)),r.progressBar&&(f.addClass("toast-progress"),l.prepend(f)),l.hide(),r.newestOnTop?v.prepend(l):v.append(l),l[r.showMethod]({duration:r.showDuration,easing:r.showEasing,complete:r.onShown}),r.timeOut>0&&(u=setTimeout(i,r.timeOut),O.maxHideTime=parseFloat(r.timeOut),O.hideEta=(new Date).getTime()+O.maxHideTime,r.progressBar&&(O.intervalId=setInterval(a,10))),l.hover(s,o),!r.onclick&&r.tapToDismiss&&l.click(i),r.closeButton&&w&&w.click(function(e){e.stopPropagation?e.stopPropagation():void 0!==e.cancelBubble&&e.cancelBubble!==!0&&(e.cancelBubble=!0),i(!0)}),r.onclick&&l.click(function(){r.onclick(),i()}),m(b),r.debug&&console&&console.log(b),l}function g(){return e.extend({},p(),b.options)}function h(e){v||(v=n()),e.is(":visible")||(e.remove(),e=null,0===v.children().length&&(v.remove(),C=void 0))}var v,w,C,T=0,O={error:"error",info:"info",success:"success",warning:"warning"},b={clear:r,remove:d,error:t,getContainer:n,info:i,options:{},subscribe:o,success:s,version:"2.1.0",warning:a};return b}()})}("function"==typeof define&&define.amd?define:function(e,t){"undefined"!=typeof module&&module.exports?module.exports=t(require("jquery")):window.toastr=t(window.jQuery)}); 2 | toastr.options = { 3 | "closeButton": true, 4 | "progressBar": true, 5 | "positionClass": "toast-top-center", 6 | "onclick": null, 7 | "hideDuration": "1000", 8 | "timeOut": "2500", 9 | "extendedTimeOut": "1000", 10 | "showEasing": "swing", 11 | "hideEasing": "linear", 12 | "showMethod": "fadeIn", 13 | "hideMethod": "fadeOut" 14 | }; 15 | -------------------------------------------------------------------------------- /src/test/resources/template/entity.java.ftl: -------------------------------------------------------------------------------- 1 | package ${package.Entity}; 2 | 3 | <#list table.importPackages as pkg> 4 | import ${pkg}; 5 | 6 | <#if swagger> 7 | import io.swagger.annotations.ApiModel; 8 | import io.swagger.annotations.ApiModelProperty; 9 | 10 | <#if entityLombokModel> 11 | import lombok.Data; 12 | import lombok.EqualsAndHashCode; 13 | <#if chainModel> 14 | import lombok.experimental.Accessors; 15 | 16 | 17 | 18 | /** 19 | *

20 | * ${table.comment!} 21 | *

22 | * 23 | * @author ${author} 24 | * @since ${date} 25 | */ 26 | <#if entityLombokModel> 27 | @Data 28 | <#if superEntityClass??> 29 | @EqualsAndHashCode(callSuper = true) 30 | <#else> 31 | @EqualsAndHashCode(callSuper = false) 32 | 33 | <#if chainModel> 34 | @Accessors(chain = true) 35 | 36 | 37 | <#if table.convert> 38 | @TableName(value = "${table.name}", schema = "${schemaName?replace(".","")}") 39 | 40 | <#if swagger> 41 | @ApiModel(value = "${entity}对象", description = "${table.comment!}") 42 | 43 | <#if superEntityClass??> 44 | public class ${entity} extends ${superEntityClass}<#if activeRecord><${entity}> { 45 | <#elseif activeRecord> 46 | public class ${entity} extends Model<${entity}> { 47 | <#else> 48 | public class ${entity} implements Serializable { 49 | 50 | 51 | <#if entitySerialVersionUID> 52 | private static final long serialVersionUID = 1L; 53 | 54 | <#-- ---------- BEGIN 字段循环遍历 ----------> 55 | <#list table.fields as field> 56 | <#if field.keyFlag> 57 | <#assign keyPropertyName="${field.propertyName}"/> 58 | 59 | 60 | <#if field.comment!?length gt 0> 61 | <#if swagger> 62 | @ApiModelProperty(value = "${field.comment}") 63 | <#else> 64 | /** 65 | * ${field.comment} 66 | */ 67 | 68 | 69 | <#if field.keyFlag> 70 | <#-- 主键 --> 71 | <#if field.keyIdentityFlag> 72 | @TableId(value = "${field.annotationColumnName}", type = IdType.AUTO) 73 | <#elseif idType??> 74 | @TableId(value = "${field.annotationColumnName}", type = IdType.${idType}) 75 | <#else> 76 | @TableId("${field.annotationColumnName}") 77 | 78 | <#-- 普通字段 --> 79 | <#elseif field.fill??> 80 | <#-- ----- 存在字段填充设置 -----> 81 | @TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill}) 82 | <#else> 83 | @TableField("${field.annotationColumnName}") 84 | 85 | <#-- 乐观锁注解 --> 86 | <#if field.versionField> 87 | @Version 88 | 89 | <#-- 逻辑删除注解 --> 90 | <#if field.logicDeleteField> 91 | @TableLogic 92 | 93 | private ${field.propertyType} ${field.propertyName}; 94 | 95 | <#------------ END 字段循环遍历 ----------> 96 | 97 | <#if !entityLombokModel> 98 | <#list table.fields as field> 99 | <#if field.propertyType == "boolean"> 100 | <#assign getprefix="is"/> 101 | <#else> 102 | <#assign getprefix="get"/> 103 | 104 | public ${field.propertyType} ${getprefix}${field.capitalName}() { 105 | return ${field.propertyName}; 106 | } 107 | 108 | <#if chainModel> 109 | public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) { 110 | <#else> 111 | public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) { 112 | 113 | this.${field.propertyName} = ${field.propertyName}; 114 | <#if chainModel> 115 | return this; 116 | 117 | } 118 | 119 | 120 | 121 | <#if entityColumnConstant> 122 | <#list table.fields as field> 123 | public static final String ${field.name?upper_case} = "${field.name}"; 124 | 125 | 126 | 127 | <#if activeRecord> 128 | @Override 129 | protected Serializable pkVal() { 130 | <#if keyPropertyName??> 131 | return this.${keyPropertyName}; 132 | <#else> 133 | return null; 134 | 135 | } 136 | 137 | 138 | <#if !entityLombokModel> 139 | @Override 140 | public String toString() { 141 | return "${entity}{" + 142 | <#list table.fields as field> 143 | <#if field_index==0> 144 | "${field.propertyName}=" + ${field.propertyName} + 145 | <#else> 146 | ", ${field.propertyName}=" + ${field.propertyName} + 147 | 148 | 149 | "}"; 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | management: 2 | endpoints: 3 | web: 4 | base-path: /rest/actuator 5 | exposure: 6 | include: '*' 7 | server: 8 | shutdown: graceful # 优雅停服务,在容器关闭时,web服务器将不再接收新请求,并将等待活动请求完成的缓冲期。从springboot2.3.0引入的新特性 9 | servlet: 10 | context-path: / 11 | port: 8090 12 | 13 | spring: 14 | servlet: 15 | multipart: 16 | max-request-size: 200MB 17 | max-file-size: 200MB 18 | main: 19 | allow-bean-definition-overriding: true # 解决bean重复定义。设置为true,后定义bean覆盖之前定义相同名称的bean。springboot2.0.4开始支持,默认为rue;springboot2.1.0开始默认false 20 | # allow-circular-references: true # 开启循环依赖,从springboot2.6.0开始,默认禁止循环依赖 21 | application: 22 | name: springboot-freemarker # 应用名 23 | freemarker: 24 | # 模板后缀名 25 | suffix: .ftl 26 | # 文档类型 27 | content-type: text/html 28 | # 页面编码 29 | charset: UTF-8 30 | # 页面缓存:true=开启;false=关闭; 31 | cache: false 32 | # 设定模板的加载路径,多个以逗号分隔,默认: 33 | template-loader-path: classpath:/templates/ 34 | # 设置是否允许HttpServletRequest属性覆盖(隐藏)控制器生成的同名模型属性。 35 | allow-request-override: false 36 | # 设置是否允许HttpSession属性覆盖(隐藏)控制器生成的同名模型属性。 37 | allow-session-override: false 38 | # 检查模板位置是否存在。 39 | check-template-location: true 40 | # 为这种技术启用MVC视图解决方案。 41 | enabled: true 42 | # 设置是否应该在与模板合并之前将所有请求属性添加到模型中。 43 | expose-request-attributes: false 44 | # 设置是否在与模板合并之前将所有HttpSession属性添加到模型中。 45 | expose-session-attributes: false 46 | # 设置是否公开RequestContext供Spring宏库使用,名称为“SpringMacroRequestContext”。 47 | expose-spring-macro-helpers: true 48 | order: 1 49 | # 更喜欢文件系统访问模板加载。文件系统访问允许对模板更改进行热检测。 50 | prefer-file-system-access: true 51 | # 所有视图的RequestContext属性的名称。 52 | request-context-attribute: request 53 | settings: 54 | # 解决前台使用${}赋值值为空的情况 55 | classic_compatible: true 56 | # 57 | default_encoding: UTF-8 58 | # 检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试 59 | template_update_delay: 0 60 | mvc: 61 | static-path-pattern: /static/** 62 | datasource: 63 | type: com.zaxxer.hikari.HikariDataSource # 声明为hikari数据源连接池 64 | driver-class-name: com.mysql.jdbc.Driver # MYSQL驱动类 65 | # driver-class-name: org.sqlite.JDBC # sqlite驱动类 66 | dynamic: # 多数据源 67 | hikari: 68 | min-idle: 10 # 最小空闲连接 69 | max-pool-size: 30 # 最大连接数 70 | idle-timeout: 600000 # 连接最小空闲时间(单位:毫秒),10分钟 71 | max-lifetime: 1800000 # 连接最大存活时间(单位:毫秒),30分钟 72 | connection-timeout: 30000 # 连接池获取连接的最长等待时间(单位:毫秒),30秒 73 | connection-test-query: SELECT 1 # 验证查询 74 | primary: db1 # 设置默认数据源 75 | strict: false # 严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源 76 | datasource: 77 | db1: 78 | username: root 79 | password: root 80 | url: 'jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=UTF-8&useSSL=false' 81 | # username: 82 | # password: 83 | # sqlite3 建议使用绝对路径,这里使用的是相对路径 84 | # url: 'jdbc:sqlite::resource:db/sqlite/db1.sqlite3' 85 | db2: 86 | username: root 87 | password: root 88 | url: 'jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=UTF-8&useSSL=false' 89 | # username: 90 | # password: 91 | # sqlite3 建议使用绝对路径,这里使用的是相对路径 92 | # url: 'jdbc:sqlite::resource:db/sqlite/db2.sqlite3' 93 | # ldap: 94 | # urls: ldap://127.0.0.1:389 95 | # base: OU=公司名称,DC=nutcracker,DC=com,DC=cn 96 | # username: admin 97 | # password: 123 98 | mybatis-plus: 99 | configuration: 100 | default-fetch-size: 100 # 每批处理的大小 101 | map-underscore-to-camel-case: true # 将下划线转为驼峰命名 102 | 103 | locks: 104 | zookeeper: # zookeeper分布式锁 105 | namespace: locks/springboot-freemarker # 锁命名空间 106 | # connect-server: zookeeper01:2181,zookeeper02:2181,zookeeper03:2181 # 连接地址 107 | connect-server: 127.0.0.1:2181 # 连接地址 108 | connection-timeout: 30000 # 连接超时时间,30秒 109 | session-timeout: 30000 # session会话超时时间,30秒 110 | app: 111 | version: '@version@' 112 | 113 | logging: 114 | config: classpath:log4j2.xml 115 | -------------------------------------------------------------------------------- /src/main/resources/static/js/plugins/slimscroll/jquery.slimscroll.min.js: -------------------------------------------------------------------------------- 1 | /*! Copyright (c) 2011 Piotr Rochala (http://rocha.la) 2 | * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 3 | * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. 4 | * 5 | * Version: 1.3.8 6 | * 7 | */ 8 | (function(e){e.fn.extend({slimScroll:function(f){var a=e.extend({width:"auto",height:"250px",size:"7px",color:"#000",position:"right",distance:"1px",start:"top",opacity:.4,alwaysVisible:!1,disableFadeOut:!1,railVisible:!1,railColor:"#333",railOpacity:.2,railDraggable:!0,railClass:"slimScrollRail",barClass:"slimScrollBar",wrapperClass:"slimScrollDiv",allowPageScroll:!1,wheelStep:20,touchScrollStep:200,borderRadius:"7px",railBorderRadius:"7px"},f);this.each(function(){function v(d){if(r){d=d||window.event; 9 | var c=0;d.wheelDelta&&(c=-d.wheelDelta/120);d.detail&&(c=d.detail/3);e(d.target||d.srcTarget||d.srcElement).closest("."+a.wrapperClass).is(b.parent())&&n(c,!0);d.preventDefault&&!k&&d.preventDefault();k||(d.returnValue=!1)}}function n(d,e,f){k=!1;var g=d,h=b.outerHeight()-c.outerHeight();e&&(g=parseInt(c.css("top"))+d*parseInt(a.wheelStep)/100*c.outerHeight(),g=Math.min(Math.max(g,0),h),g=0=b.outerHeight()?k=!0:(c.stop(!0, 11 | !0).fadeIn("fast"),a.railVisible&&m.stop(!0,!0).fadeIn("fast"))}function q(){a.alwaysVisible||(B=setTimeout(function(){a.disableFadeOut&&r||y||z||(c.fadeOut("slow"),m.fadeOut("slow"))},1E3))}var r,y,z,B,A,u,l,C,k=!1,b=e(this);if(b.parent().hasClass(a.wrapperClass)){var p=b.scrollTop(),c=b.siblings("."+a.barClass),m=b.siblings("."+a.railClass);x();if(e.isPlainObject(f)){if("height"in f&&"auto"==f.height){b.parent().css("height","auto");b.css("height","auto");var h=b.parent().parent().height();b.parent().css("height", 12 | h);b.css("height",h)}else"height"in f&&(h=f.height,b.parent().css("height",h),b.css("height",h));if("scrollTo"in f)p=parseInt(a.scrollTo);else if("scrollBy"in f)p+=parseInt(a.scrollBy);else if("destroy"in f){c.remove();m.remove();b.unwrap();return}n(p,!1,!0)}}else if(!(e.isPlainObject(f)&&"destroy"in f)){a.height="auto"==a.height?b.parent().height():a.height;p=e("
").addClass(a.wrapperClass).css({position:"relative",overflow:"hidden",width:a.width,height:a.height});b.css({overflow:"hidden", 13 | width:a.width,height:a.height});var m=e("
").addClass(a.railClass).css({width:a.size,height:"100%",position:"absolute",top:0,display:a.alwaysVisible&&a.railVisible?"block":"none","border-radius":a.railBorderRadius,background:a.railColor,opacity:a.railOpacity,zIndex:90}),c=e("
").addClass(a.barClass).css({background:a.color,width:a.size,position:"absolute",top:0,opacity:a.opacity,display:a.alwaysVisible?"block":"none","border-radius":a.borderRadius,BorderRadius:a.borderRadius,MozBorderRadius:a.borderRadius, 14 | WebkitBorderRadius:a.borderRadius,zIndex:99}),h="right"==a.position?{right:a.distance}:{left:a.distance};m.css(h);c.css(h);b.wrap(p);b.parent().append(c);b.parent().append(m);a.railDraggable&&c.bind("mousedown",function(a){var b=e(document);z=!0;t=parseFloat(c.css("top"));pageY=a.pageY;b.bind("mousemove.slimscroll",function(a){currTop=t+a.pageY-pageY;c.css("top",currTop);n(0,c.position().top,!1)});b.bind("mouseup.slimscroll",function(a){z=!1;q();b.unbind(".slimscroll")});return!1}).bind("selectstart.slimscroll", 15 | function(a){a.stopPropagation();a.preventDefault();return!1});m.hover(function(){w()},function(){q()});c.hover(function(){y=!0},function(){y=!1});b.hover(function(){r=!0;w();q()},function(){r=!1;q()});b.bind("touchstart",function(a,b){a.originalEvent.touches.length&&(A=a.originalEvent.touches[0].pageY)});b.bind("touchmove",function(b){k||b.originalEvent.preventDefault();b.originalEvent.touches.length&&(n((A-b.originalEvent.touches[0].pageY)/a.touchScrollStep,!0),A=b.originalEvent.touches[0].pageY)}); 16 | x();"bottom"===a.start?(c.css({top:b.outerHeight()-c.outerHeight()}),n(0,!0)):"top"!==a.start&&(n(e(a.start).position().top,null,!0),a.alwaysVisible||c.hide());window.addEventListener?(this.addEventListener("DOMMouseScroll",v,!1),this.addEventListener("mousewheel",v,!1)):document.attachEvent("onmousewheel",v)}});return this}});e.fn.extend({slimscroll:e.fn.slimScroll})})(jQuery); -------------------------------------------------------------------------------- /src/main/java/cn/springboot/config/authority/filter/XSSSecurityFilter.java: -------------------------------------------------------------------------------- 1 | package cn.springboot.config.authority.filter; 2 | 3 | import cn.springboot.config.authority.service.xss.XSSHttpRequestWrapper; 4 | import cn.springboot.config.authority.service.xss.XSSSecurityConfig; 5 | import cn.springboot.config.authority.service.xss.XSSSecurityConstants; 6 | import cn.springboot.config.authority.service.xss.XSSSecurityManager; 7 | import com.alibaba.fastjson2.JSON; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import javax.servlet.Filter; 12 | import javax.servlet.FilterChain; 13 | import javax.servlet.FilterConfig; 14 | import javax.servlet.ServletException; 15 | import javax.servlet.ServletRequest; 16 | import javax.servlet.ServletResponse; 17 | import javax.servlet.annotation.WebFilter; 18 | import javax.servlet.annotation.WebInitParam; 19 | import javax.servlet.http.HttpServletRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | import java.io.IOException; 22 | import java.util.Arrays; 23 | import java.util.Map; 24 | import java.util.Set; 25 | 26 | /** 27 | * @author 胡桃夹子 28 | * @Description xss攻击脚本过滤器 29 | * @date Mar 24, 2017 7:43:01 PM 30 | */ 31 | @WebFilter(urlPatterns = "/*", filterName = "XSSCheck", initParams = {@WebInitParam(name = "securityconfig", value = "classpath:conf/xss_security_config.xml")}) 32 | public class XSSSecurityFilter implements Filter { 33 | 34 | private static final Logger log = LoggerFactory.getLogger(XSSSecurityFilter.class); 35 | 36 | /** 37 | * 初始化操作 38 | */ 39 | @Override 40 | public void init(FilterConfig filterConfig) throws ServletException { 41 | XSSSecurityManager.init(filterConfig); 42 | } 43 | 44 | /** 45 | * 销毁操作 46 | */ 47 | @Override 48 | public void destroy() { 49 | log.debug("XSSSecurityFilter destroy() begin"); 50 | XSSSecurityManager.destroy(); 51 | log.debug("XSSSecurityFilter destroy() end"); 52 | } 53 | 54 | /** 55 | * 安全审核 读取配置信息 56 | */ 57 | @Override 58 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 59 | // 判断是否使用HTTP 60 | checkRequestResponse(request, response); 61 | 62 | HttpServletRequest httpRequest = (HttpServletRequest) request; 63 | HttpServletResponse httpResponse = (HttpServletResponse) response; 64 | 65 | // http信息封装类 66 | XSSHttpRequestWrapper xssRequest = new XSSHttpRequestWrapper(httpRequest); 67 | 68 | // 对request信息进行封装并进行校验工作,若校验失败(含非法字符),根据配置信息进行日志记录和请求中断处理 69 | if (xssRequest.validateParameter(httpResponse)) { 70 | if (XSSSecurityConfig.IS_LOG) { 71 | StringBuilder paramStr = new StringBuilder(); 72 | Map submitParams = httpRequest.getParameterMap(); 73 | Set submitNames = submitParams.keySet(); 74 | String[] submitValues; 75 | for (String submitName : submitNames) { 76 | submitValues = submitParams.get(submitName); 77 | Arrays.stream(submitValues).forEachOrdered(paramStr::append); 78 | } 79 | 80 | log.debug("XSS Security Filter RequestURL:{}", httpRequest.getRequestURL()); 81 | log.debug("param:{}", paramStr); 82 | log.debug("XSS Security Filter RequestParameter:{}", JSON.toJSONString(httpRequest.getParameterMap())); 83 | } 84 | // 是否中断操作 85 | if (XSSSecurityConfig.IS_CHAIN) { 86 | request.setAttribute("err", "您输入的参数有非法字符,请输入正确的参数!"); 87 | request.setAttribute("pageUrl", httpRequest.getRequestURI()); 88 | request.getRequestDispatcher(request.getServletContext().getContextPath() + XSSSecurityConstants.FILTER_ERROR_PAGE).forward(request, response); 89 | return; 90 | } 91 | 92 | } 93 | chain.doFilter(request, response); 94 | } 95 | 96 | /** 97 | * 判断Request ,Response 类型 98 | * 99 | * @param request ServletRequest 100 | * @param response ServletResponse 101 | */ 102 | private void checkRequestResponse(ServletRequest request, ServletResponse response) throws ServletException { 103 | if (!(request instanceof HttpServletRequest)) { 104 | throw new ServletException("Can only process HttpServletRequest"); 105 | } 106 | 107 | if (!(response instanceof HttpServletResponse)) { 108 | throw new ServletException("Can only process HttpServletResponse"); 109 | } 110 | } 111 | } 112 | --------------------------------------------------------------------------------