├── docs ├── README.md ├── plugin.md ├── starter.md ├── extension.md ├── structure.md └── logging.md ├── demo ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── lin │ │ │ │ └── cms │ │ │ │ └── demo │ │ │ │ ├── extensions │ │ │ │ ├── limit │ │ │ │ │ └── .gitkeep │ │ │ │ └── file │ │ │ │ │ ├── PreHandler.java │ │ │ │ │ ├── config.properties │ │ │ │ │ ├── FileConsts.java │ │ │ │ │ ├── Uploader.java │ │ │ │ │ ├── File.java │ │ │ │ │ ├── FileProperties.java │ │ │ │ │ └── FileUtil.java │ │ │ │ ├── common │ │ │ │ ├── consts │ │ │ │ │ └── IdentityConsts.java │ │ │ │ ├── LocalUser.java │ │ │ │ ├── configure │ │ │ │ │ ├── CodeConfig.java │ │ │ │ │ └── CommonConfig.java │ │ │ │ ├── aop │ │ │ │ │ └── ResultAspect.java │ │ │ │ ├── interceptor │ │ │ │ │ ├── RequestLogInterceptor.java │ │ │ │ │ └── LoggerImpl.java │ │ │ │ └── utils │ │ │ │ │ └── ResponseUtil.java │ │ │ │ ├── mapper │ │ │ │ ├── UserIdentityMapper.java │ │ │ │ ├── BookMapper.java │ │ │ │ ├── FileMapper.java │ │ │ │ ├── UserGroupMapper.java │ │ │ │ ├── GroupPermissionMapper.java │ │ │ │ ├── LogMapper.java │ │ │ │ ├── PermissionMapper.java │ │ │ │ ├── UserMapper.java │ │ │ │ └── GroupMapper.java │ │ │ │ ├── bo │ │ │ │ ├── FileBO.java │ │ │ │ └── GroupPermissionsBO.java │ │ │ │ ├── dto │ │ │ │ ├── admin │ │ │ │ │ ├── UpdateGroupDTO.java │ │ │ │ │ ├── UpdateUserInfoDTO.java │ │ │ │ │ ├── RemovePermissionsDTO.java │ │ │ │ │ ├── DispatchPermissionsDTO.java │ │ │ │ │ ├── DispatchPermissionDTO.java │ │ │ │ │ ├── NewGroupDTO.java │ │ │ │ │ └── ResetPasswordDTO.java │ │ │ │ ├── user │ │ │ │ │ ├── LoginDTO.java │ │ │ │ │ ├── UpdateInfoDTO.java │ │ │ │ │ ├── ChangePasswordDTO.java │ │ │ │ │ └── RegisterDTO.java │ │ │ │ └── book │ │ │ │ │ └── CreateOrUpdateBookDTO.java │ │ │ │ ├── vo │ │ │ │ ├── UnifyResponseVO.java │ │ │ │ ├── PageResponseVO.java │ │ │ │ ├── UserPermissionsVO.java │ │ │ │ └── UserInfoVO.java │ │ │ │ ├── service │ │ │ │ ├── BookService.java │ │ │ │ ├── LogService.java │ │ │ │ ├── FileService.java │ │ │ │ ├── AdminService.java │ │ │ │ ├── PermissionService.java │ │ │ │ ├── impl │ │ │ │ │ ├── BookServiceImpl.java │ │ │ │ │ ├── LogServiceImpl.java │ │ │ │ │ └── FileServiceImpl.java │ │ │ │ ├── GroupService.java │ │ │ │ ├── UserIdentityService.java │ │ │ │ └── UserService.java │ │ │ │ ├── model │ │ │ │ ├── BookDO.java │ │ │ │ ├── UserGroupDO.java │ │ │ │ ├── GroupPermissionDO.java │ │ │ │ ├── GroupDO.java │ │ │ │ ├── PermissionDO.java │ │ │ │ ├── FileDO.java │ │ │ │ ├── UserDO.java │ │ │ │ ├── LogDO.java │ │ │ │ └── UserIdentityDO.java │ │ │ │ ├── controller │ │ │ │ ├── cms │ │ │ │ │ ├── FileController.java │ │ │ │ │ └── TestController.java │ │ │ │ └── v1 │ │ │ │ │ └── BookController.java │ │ │ │ └── DemoApplication.java │ │ └── resources │ │ │ ├── application-test.properties │ │ │ ├── mapper │ │ │ ├── UserIdentityMapper.xml │ │ │ ├── UserGroupMapper.xml │ │ │ ├── GroupPermissionMapper.xml │ │ │ ├── FileMapper.xml │ │ │ ├── BookMapper.xml │ │ │ ├── UserMapper.xml │ │ │ ├── PermissionMapper.xml │ │ │ ├── LogMapper.xml │ │ │ └── GroupMapper.xml │ │ │ ├── application-dev.properties │ │ │ ├── application.properties │ │ │ ├── banner.txt │ │ │ └── logback-spring.xml │ └── test │ │ └── java │ │ ├── com │ │ └── lin │ │ │ └── cms │ │ │ └── demo │ │ │ ├── DemoApplicationTests.java │ │ │ └── mapper │ │ │ ├── FileMapperTest.java │ │ │ ├── BookMapperTest.java │ │ │ ├── LogMapperTest.java │ │ │ └── UserMapperTest.java │ │ ├── FileTest.java │ │ └── JsonTest.java ├── finereport.md ├── code.md ├── authority.md └── README.md ├── lin-cms-spring-boot-starter ├── src │ └── main │ │ └── resources │ │ └── application.properties └── pom.xml ├── poem └── src │ ├── main │ └── java │ │ └── com │ │ └── lin │ │ └── cms │ │ └── plugins │ │ └── poem │ │ ├── plugin.properties │ │ ├── app │ │ ├── PoemMapper.java │ │ ├── PoemDO.java │ │ ├── PageResult.java │ │ └── PoemController.java │ │ ├── PoemProperties.java │ │ └── PoemMapper.xml │ └── test │ ├── java │ ├── auth │ │ ├── LoggerImpl.java │ │ ├── AuthVerifyResolverImpl.java │ │ └── WebConfig.java │ └── MockApplication.java │ └── resources │ └── application.properties ├── lin-cms-spring-boot-autoconfigure ├── src │ ├── main │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── spring.factories │ │ └── java │ │ │ └── com │ │ │ └── lin │ │ │ └── cms │ │ │ ├── interfaces │ │ │ ├── MetaPreHandler.java │ │ │ ├── BaseResponse.java │ │ │ ├── LoggerResolver.java │ │ │ └── AuthorizeVerifyResolver.java │ │ │ ├── validator │ │ │ ├── Length.java │ │ │ ├── DateTimeFormat.java │ │ │ ├── LongList.java │ │ │ ├── Enum.java │ │ │ ├── NotEmptyFields.java │ │ │ ├── EqualField.java │ │ │ └── impl │ │ │ │ ├── NotEmptyFieldsValidator.java │ │ │ │ ├── LongListValidator.java │ │ │ │ ├── LengthValidator.java │ │ │ │ ├── DateTimeValidator.java │ │ │ │ ├── EqualFieldValidator.java │ │ │ │ └── EnumValidator.java │ │ │ ├── response │ │ │ ├── Success.java │ │ │ └── Created.java │ │ │ ├── exception │ │ │ ├── FailedException.java │ │ │ ├── RepeatException.java │ │ │ ├── NotFoundException.java │ │ │ ├── ForbiddenException.java │ │ │ ├── TokenExpiredException.java │ │ │ ├── TokenInvalidException.java │ │ │ ├── RequestLimitException.java │ │ │ ├── FileExtensionException.java │ │ │ ├── FileTooManyException.java │ │ │ ├── RefreshFailedException.java │ │ │ ├── FileTooLargeException.java │ │ │ ├── MethodNotAllowedException.java │ │ │ ├── AuthenticationException.java │ │ │ ├── AuthorizationException.java │ │ │ ├── ParameterException.java │ │ │ └── HttpException.java │ │ │ ├── utils │ │ │ └── RequestUtil.java │ │ │ ├── interceptor │ │ │ └── LogInterceptor.java │ │ │ ├── autoconfigure │ │ │ ├── LinCmsProperties.java │ │ │ └── LinCmsAutoConfigure.java │ │ │ └── beans │ │ │ └── Code.java │ └── test │ │ └── java │ │ └── com │ │ └── lin │ │ └── cms │ │ └── exception │ │ └── ParameterExceptionTest.java └── pom.xml ├── core ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── lin │ │ │ └── cms │ │ │ └── core │ │ │ ├── enums │ │ │ └── UserLevel.java │ │ │ ├── annotation │ │ │ ├── Logger.java │ │ │ ├── AdminRequired.java │ │ │ ├── GroupRequired.java │ │ │ ├── LoginRequired.java │ │ │ ├── RefreshRequired.java │ │ │ ├── Required.java │ │ │ └── RouteMeta.java │ │ │ ├── token │ │ │ ├── Tokens.java │ │ │ └── SingleJWT.java │ │ │ ├── consts │ │ │ └── TokenConst.java │ │ │ └── utils │ │ │ ├── DateUtil.java │ │ │ ├── AnnotationUtil.java │ │ │ └── EncryptUtil.java │ └── test │ │ └── java │ │ └── com │ │ └── lin │ │ └── cms │ │ └── core │ │ ├── utils │ │ └── DateUtilTest.java │ │ └── token │ │ └── SingleJWTTest.java ├── README.md └── pom.xml ├── .gitignore ├── README.md ├── .github └── workflows │ └── maven.yml ├── LICENSE └── pom.xml /docs/README.md: -------------------------------------------------------------------------------- 1 | # 快速上手 2 | 3 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/extensions/limit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-starter/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /poem/src/main/java/com/lin/cms/plugins/poem/plugin.properties: -------------------------------------------------------------------------------- 1 | # 2 | # plugin test config 3 | lin.cms.poem.limit=3 -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.lin.cms.autoconfigure.LinCmsAutoConfigure -------------------------------------------------------------------------------- /demo/finereport.md: -------------------------------------------------------------------------------- 1 | # finereport 2 | 3 | ## 思路 4 | 5 | - 系统总管理员(root)在权限管理中开启分级授权选项,将授权权限赋给下级管理员的角色,同时 6 | 配置该角色所能分配权限的角色(对象),此时,下级管理员的角色登录系统时,就能将其 7 | 有权授权的权限分配给对应的角色。 8 | - 查看权限和授权权限 9 | - 次级管理员必须拥有权限管理权限后,才能拥有授权权限 10 | - -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/interfaces/MetaPreHandler.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.interfaces; 2 | 3 | import com.lin.cms.core.annotation.RouteMeta; 4 | 5 | public interface MetaPreHandler { 6 | 7 | void handle(RouteMeta meta); 8 | } 9 | -------------------------------------------------------------------------------- /core/src/main/java/com/lin/cms/core/enums/UserLevel.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.core.enums; 2 | 3 | public enum UserLevel { 4 | 5 | TOURIST, // 游客即可访问 6 | 7 | LOGIN, // 登录才可访问 8 | 9 | GROUP, // 登录有权限才可访问 10 | 11 | ADMIN, // 管理员权限 12 | 13 | REFRESH // 令牌刷新 14 | } 15 | -------------------------------------------------------------------------------- /core/src/main/java/com/lin/cms/core/annotation/Logger.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.core.annotation; 2 | 3 | 4 | import java.lang.annotation.*; 5 | 6 | @Target(ElementType.METHOD) 7 | @Retention(RetentionPolicy.RUNTIME) 8 | @Documented 9 | public @interface Logger { 10 | String template(); 11 | } 12 | -------------------------------------------------------------------------------- /core/src/main/java/com/lin/cms/core/token/Tokens.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.core.token; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @AllArgsConstructor 7 | public class Tokens { 8 | 9 | @Getter 10 | private String accessToken; 11 | 12 | @Getter 13 | private String refreshToken; 14 | } 15 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/common/consts/IdentityConsts.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.common.consts; 2 | 3 | /** 4 | * 身份认证常量 5 | */ 6 | public class IdentityConsts { 7 | 8 | /** 9 | * 表示通过用户名和密码来进行身份认证 10 | */ 11 | public static final String USERNAME_PASSWORD_IDENTITY = "USERNAME_PASSWORD"; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/extensions/file/PreHandler.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.extensions.file; 2 | 3 | /** 4 | * 文件前预处理器 5 | */ 6 | public interface PreHandler { 7 | 8 | /** 9 | * 在文件写入本地或者上传到云之前调用此方法 10 | * 11 | * @return 是否上传,若返回false,则不上传 12 | */ 13 | boolean handle(File file); 14 | } 15 | -------------------------------------------------------------------------------- /core/src/main/java/com/lin/cms/core/annotation/AdminRequired.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.core.annotation; 2 | 3 | import com.lin.cms.core.enums.UserLevel; 4 | 5 | import java.lang.annotation.*; 6 | 7 | @Target(ElementType.METHOD) 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Documented 10 | @Required(level = UserLevel.ADMIN) 11 | public @interface AdminRequired { 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/java/com/lin/cms/core/annotation/GroupRequired.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.core.annotation; 2 | 3 | import com.lin.cms.core.enums.UserLevel; 4 | 5 | import java.lang.annotation.*; 6 | 7 | @Target(ElementType.METHOD) 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Documented 10 | @Required(level = UserLevel.GROUP) 11 | public @interface GroupRequired { 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/java/com/lin/cms/core/annotation/LoginRequired.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.core.annotation; 2 | 3 | 4 | import com.lin.cms.core.enums.UserLevel; 5 | 6 | import java.lang.annotation.*; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Documented 11 | @Required(level = UserLevel.LOGIN) 12 | public @interface LoginRequired { 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/java/com/lin/cms/core/annotation/RefreshRequired.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.core.annotation; 2 | 3 | import com.lin.cms.core.enums.UserLevel; 4 | 5 | import java.lang.annotation.*; 6 | 7 | @Target(ElementType.METHOD) 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Documented 10 | @Required(level = UserLevel.REFRESH) 11 | public @interface RefreshRequired { 12 | } 13 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/mapper/UserIdentityMapper.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.mapper; 2 | 3 | import com.lin.cms.demo.model.UserIdentityDO; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | * @author pedro 8 | * @since 2019-12-02 9 | */ 10 | public interface UserIdentityMapper extends BaseMapper { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/java/com/lin/cms/core/annotation/Required.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.core.annotation; 2 | 3 | import com.lin.cms.core.enums.UserLevel; 4 | 5 | import java.lang.annotation.*; 6 | 7 | @Target({ElementType.METHOD, ElementType.TYPE}) 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Documented 10 | public @interface Required { 11 | UserLevel level() default UserLevel.TOURIST; 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/java/com/lin/cms/core/annotation/RouteMeta.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.core.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Target(ElementType.METHOD) 6 | @Retention(RetentionPolicy.RUNTIME) 7 | @Documented 8 | public @interface RouteMeta { 9 | 10 | String permission() default ""; 11 | 12 | String module() default ""; 13 | 14 | boolean mount() default false; 15 | } 16 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/extensions/file/config.properties: -------------------------------------------------------------------------------- 1 | # upload 2 | # \u53EA\u80FD\u4ECEmax-file-size\u8BBE\u7F6E\u603B\u4F53\u6587\u4EF6\u7684\u5927\u5C0F 3 | spring.servlet.multipart.max-file-size=20MB 4 | lin.cms.file.single-limit=2MB 5 | lin.cms.file.nums=10 6 | lin.cms.file.exclude= 7 | lin.cms.file.include=.jpg,.png,.jpeg 8 | lin.cms.file.domain=http://localhost:5000/ 9 | lin.cms.file.store-dir=assets/ -------------------------------------------------------------------------------- /core/src/main/java/com/lin/cms/core/consts/TokenConst.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.core.consts; 2 | 3 | /** 4 | * 令牌相关常量 5 | */ 6 | public class TokenConst { 7 | 8 | public final static String ACCESS_TYPE = "access"; 9 | 10 | public final static String REFRESH_TYPE = "refresh"; 11 | 12 | public final static String LIN_SCOPE = "lin"; 13 | 14 | public final static String OTHER_SCOPE = "other"; 15 | } 16 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/bo/FileBO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.bo; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class FileBO { 7 | 8 | private Long id; 9 | 10 | private String path; 11 | 12 | /** 13 | * LOCAL REMOTE 14 | */ 15 | private String type; 16 | 17 | private String name; 18 | 19 | private String extension; 20 | 21 | private Integer size; 22 | } 23 | -------------------------------------------------------------------------------- /poem/src/main/java/com/lin/cms/plugins/poem/app/PoemMapper.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.plugins.poem.app; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import org.apache.ibatis.annotations.Param; 5 | 6 | import java.util.List; 7 | 8 | public interface PoemMapper extends BaseMapper { 9 | 10 | Integer getCount(); 11 | 12 | List findPoemsByAuthor(@Param("author") String author); 13 | } -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 | # lin-cms core 2 | 3 | > lin-cms 核心库部分 4 | 5 | ## 原则 6 | 7 | 与spring boot 解耦合,即不依赖任何 spring boot 的库,只依赖其它有用的第三方库 8 | 9 | 如 jhash,jwt 等。。。 10 | 11 | 然后提供 lin-spring-boot-starter 和 lin-spring-boot-autoconfigure 两个可用的spring boot依赖方便开发 12 | 13 | 最后提供 lin-cms-java 示例工程项目。 14 | 15 | ## core(核心库)的功能如下 16 | 17 | - 基础异常(HttpException) 18 | 19 | - 返回结果(Result和PageResult) 20 | 21 | - 诸多枚举 22 | 23 | - 诸多注解 24 | 25 | - 其它待补充 -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/dto/admin/UpdateGroupDTO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.dto.admin; 2 | 3 | import com.lin.cms.validator.Length; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class UpdateGroupDTO { 8 | 9 | @Length(min = 1, max = 60, message = "{group.name.length}") 10 | private String name; 11 | 12 | @Length(min = 1, max = 255, message = "{group.info.length}") 13 | private String info; 14 | } 15 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/extensions/file/FileConsts.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.extensions.file; 2 | 3 | /** 4 | * 文件相关常量值 5 | * 6 | * @author pedro 7 | * @since 2019-11-30 8 | */ 9 | public class FileConsts { 10 | 11 | /** 12 | * 本地文件 13 | */ 14 | public static final String LOCAL = "LOCAL"; 15 | 16 | /** 17 | * 远程文件,例如OSS 18 | */ 19 | public static final String REMOTE = "REMOTE"; 20 | } 21 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/interfaces/BaseResponse.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.interfaces; 2 | 3 | /** 4 | * 基础的返回接口 5 | * 必须实现以下三个方法 6 | */ 7 | public interface BaseResponse { 8 | 9 | /** 10 | * 返回的信息 11 | */ 12 | String getMessage(); 13 | 14 | /** 15 | * http 状态码 16 | */ 17 | int getHttpCode(); 18 | 19 | /** 20 | * 错误码 21 | */ 22 | int getCode(); 23 | } 24 | -------------------------------------------------------------------------------- /demo/code.md: -------------------------------------------------------------------------------- 1 | # code 2 | 3 | ## 核心库内置已使用状态码 4 | 5 | 0 成功 6 | 7 | 999 服务器未知错误 8 | 9 | 9999 失败 10 | 11 | 10000 认证失败 12 | 13 | 10020 资源不存在 14 | 15 | 10030 参数错误 16 | 17 | 10040 令牌失效 18 | 19 | 10050 令牌过期 20 | 21 | 10060 字段重复 22 | 23 | 10070 禁止操作 24 | 25 | 10080 请求方法不允许 26 | 27 | 10100 refresh token 获取失败 28 | 29 | 10110 文件体积过大 30 | 31 | 10120 文件数量过多 32 | 33 | 10130 文件扩展名不符合规范 34 | 35 | 10140 请求过于频繁,请稍后重试 36 | 37 | ## 项目使用的状态码 38 | 39 | 80010 图书未找到 40 | -------------------------------------------------------------------------------- /core/src/main/java/com/lin/cms/core/utils/DateUtil.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.core.utils; 2 | 3 | import java.util.Date; 4 | 5 | public class DateUtil { 6 | 7 | /** 8 | * 获得过期时间 9 | * 10 | * @param duration 延时时间,单位s 11 | * @return Date 12 | */ 13 | public static Date getDurationDate(long duration) { 14 | long expireTime = System.currentTimeMillis() + duration * 1000; 15 | return new Date(expireTime); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/dto/admin/UpdateUserInfoDTO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.dto.admin; 2 | 3 | import com.lin.cms.validator.LongList; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | import java.util.List; 9 | 10 | @Setter 11 | @Getter 12 | @NoArgsConstructor 13 | public class UpdateUserInfoDTO { 14 | 15 | @LongList(min = 1, message = "{group.ids.long-list}") 16 | private List groupIds; 17 | } 18 | -------------------------------------------------------------------------------- /poem/src/main/java/com/lin/cms/plugins/poem/PoemProperties.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.plugins.poem; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | @ConfigurationProperties("lin.cms.poem") 6 | public class PoemProperties { 7 | 8 | private Integer limit; 9 | 10 | public Integer getLimit() { 11 | return limit; 12 | } 13 | 14 | public void setLimit(Integer limit) { 15 | this.limit = limit; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/vo/UnifyResponseVO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.vo; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | 10 | /** 11 | * 统一API响应结果封装 12 | */ 13 | @Data 14 | @Builder 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class UnifyResponseVO { 18 | 19 | private int code; 20 | 21 | private T message; 22 | 23 | private String request; 24 | } 25 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/mapper/BookMapper.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.lin.cms.demo.model.BookDO; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | import java.util.List; 8 | 9 | public interface BookMapper extends BaseMapper { 10 | 11 | List selectByTitleLikeKeyword(@Param("q") String q); 12 | 13 | List selectByTitle(@Param("title") String title); 14 | } 15 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/mapper/FileMapper.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.mapper; 2 | 3 | import com.lin.cms.demo.model.FileDO; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | /** 8 | * @author pedro 9 | * @since 2019-11-30 10 | */ 11 | public interface FileMapper extends BaseMapper { 12 | 13 | FileDO selectByMd5(@Param("md5") String md5); 14 | 15 | int selectCountByMd5(@Param("md5") String md5); 16 | } 17 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/mapper/UserGroupMapper.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.mapper; 2 | 3 | import com.lin.cms.demo.model.UserGroupDO; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author pedro 11 | * @since 2019-11-30 12 | */ 13 | public interface UserGroupMapper extends BaseMapper { 14 | 15 | int insertBatch(@Param("relations") List relations); 16 | } 17 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/interfaces/LoggerResolver.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.interfaces; 2 | 3 | import com.lin.cms.core.annotation.Logger; 4 | import com.lin.cms.core.annotation.RouteMeta; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | /** 10 | * 行为日志记录 11 | */ 12 | public interface LoggerResolver { 13 | 14 | void handle(RouteMeta meta, Logger logger, HttpServletRequest request, HttpServletResponse response); 15 | } 16 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/dto/user/LoginDTO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.dto.user; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | 7 | import javax.validation.constraints.NotBlank; 8 | 9 | @Setter 10 | @Getter 11 | @NoArgsConstructor 12 | public class LoginDTO { 13 | 14 | @NotBlank(message = "{user.username.not-blank}") 15 | private String username; 16 | 17 | @NotBlank(message = "{password.new-password.not-blank}") 18 | private String password; 19 | } 20 | -------------------------------------------------------------------------------- /demo/src/test/java/com/lin/cms/demo/DemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.ActiveProfiles; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | @RunWith(SpringRunner.class) 10 | @SpringBootTest 11 | @ActiveProfiles("test") 12 | public class DemoApplicationTests { 13 | 14 | @Test 15 | public void contextLoads() { 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /demo/src/test/java/FileTest.java: -------------------------------------------------------------------------------- 1 | import cn.hutool.core.io.FileUtil; 2 | import lombok.extern.slf4j.Slf4j; 3 | import org.junit.Test; 4 | 5 | 6 | @Slf4j 7 | public class FileTest { 8 | 9 | @Test 10 | public void getDirServePath() { 11 | String s = FileUtil.mainName("assets/"); 12 | log.info(s); 13 | 14 | s = FileUtil.mainName("/usr/local/assets/"); 15 | log.info(s); 16 | 17 | s = FileUtil.mainName("assets"); 18 | log.info(s); 19 | 20 | s = FileUtil.mainName("assets"); 21 | log.info(s); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/validator/Length.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.validator; 2 | 3 | import javax.validation.Payload; 4 | 5 | /** 6 | * 自定义的字符串长度校验器 7 | * 可允许字符串为Blank的时候不校验 8 | */ 9 | public @interface Length { 10 | 11 | int min() default 0; 12 | 13 | int max() default Integer.MAX_VALUE; 14 | 15 | boolean allowBlank() default false; 16 | 17 | String message() default "the length of str is not satisfy"; 18 | 19 | Class[] groups() default {}; 20 | 21 | Class[] payload() default {}; 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | **/target/ 7 | 8 | **.DS_Store 9 | 10 | ### STS ### 11 | .apt_generated 12 | .classpath 13 | .factorypath 14 | .project 15 | .settings 16 | .springBeans 17 | .sts4-cache 18 | 19 | ### IntelliJ IDEA ### 20 | .idea 21 | *.iws 22 | *.iml 23 | *.ipr 24 | 25 | ### NetBeans ### 26 | /nbproject/private/ 27 | /nbbuild/ 28 | /dist/ 29 | /nbdist/ 30 | /.nb-gradle/ 31 | build/ 32 | 33 | ### VS Code ### 34 | .vscode/ 35 | assets/ 36 | logs/ 37 | 38 | 39 | application-prod.properties 40 | 41 | 42 | -------------------------------------------------------------------------------- /core/src/test/java/com/lin/cms/core/utils/DateUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.core.utils; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Date; 6 | 7 | import static org.junit.Assert.*; 8 | 9 | public class DateUtilTest { 10 | 11 | @Test 12 | public void getDurationDate() { 13 | Date durationDate = DateUtil.getDurationDate(10); 14 | long now = new Date().getTime(); 15 | assertTrue(durationDate.getTime() > now); 16 | assertTrue(durationDate.getTime() - now > 5 * 1000); 17 | assertTrue(durationDate.getTime() - now <= 10 * 1000); 18 | } 19 | } -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/service/BookService.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.service; 2 | 3 | import com.lin.cms.demo.model.BookDO; 4 | import com.lin.cms.demo.dto.book.CreateOrUpdateBookDTO; 5 | 6 | import java.util.List; 7 | 8 | public interface BookService { 9 | 10 | boolean createBook(CreateOrUpdateBookDTO validator); 11 | 12 | List getBookByKeyword(String q); 13 | 14 | boolean updateBook(BookDO book, CreateOrUpdateBookDTO validator); 15 | 16 | BookDO getById(Long id); 17 | 18 | List findAll(); 19 | 20 | boolean deleteById(Long id); 21 | } 22 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/dto/admin/RemovePermissionsDTO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.dto.admin; 2 | 3 | import com.lin.cms.validator.LongList; 4 | import lombok.Data; 5 | 6 | import javax.validation.constraints.NotNull; 7 | import javax.validation.constraints.Positive; 8 | import java.util.List; 9 | 10 | @Data 11 | public class RemovePermissionsDTO { 12 | 13 | @Positive(message = "{group.id.positive}") 14 | @NotNull(message = "{group.id.not-null}") 15 | private Long groupId; 16 | 17 | @LongList(message = "{permission.ids.long-list}") 18 | private List permissionIds; 19 | } 20 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/dto/admin/DispatchPermissionsDTO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.dto.admin; 2 | 3 | import com.lin.cms.validator.LongList; 4 | import lombok.Data; 5 | 6 | import javax.validation.constraints.NotNull; 7 | import javax.validation.constraints.Positive; 8 | import java.util.List; 9 | 10 | @Data 11 | public class DispatchPermissionsDTO { 12 | 13 | @Positive(message = "{group.id.positive}") 14 | @NotNull(message = "{group.id.not-null}") 15 | private Long groupId; 16 | 17 | @LongList(message = "{permission.ids.long-list}") 18 | private List permissionIds; 19 | } 20 | -------------------------------------------------------------------------------- /demo/src/main/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | server.port=5000 2 | # \u6D4B\u8BD5\u73AF\u5883\u73AF\u5883\u914D\u7F6E 3 | spring.datasource.url=jdbc:h2:mem:testdbsa;MODE=MYSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false 4 | spring.datasource.username=sa 5 | spring.datasource.password= 6 | spring.datasource.driver-class-name=org.h2.Driver 7 | spring.datasource.continue-on-error=false 8 | spring.datasource.platform=h2 9 | spring.datasource.schema=classpath:/h2-test.sql 10 | spring.h2.console.enabled=true 11 | spring.h2.console.path=/h2 12 | # 13 | logging.level.com.lin.cms.demo.mapper=debug 14 | logging.config=classpath:logback-spring.xml 15 | # -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/validator/DateTimeFormat.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.validator; 2 | 3 | import javax.validation.Payload; 4 | 5 | /** 6 | * 日期格式校验器 默认校验日期格式 yyyy-MM-dd HH:mm:ss 7 | * 可通过指定 format 来更改校验格式 8 | */ 9 | public @interface DateTimeFormat { 10 | 11 | String message() default "date format invalid"; 12 | 13 | String format() default "yyyy-MM-dd HH:mm:ss"; 14 | 15 | /** 16 | * 是否允许传入空的日期,如果为false,当传入空字符串时,校验失败 17 | */ 18 | boolean allowNull() default false; 19 | 20 | Class[] groups() default {}; 21 | 22 | Class[] payload() default {}; 23 | } 24 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/dto/user/UpdateInfoDTO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.dto.user; 2 | 3 | import com.lin.cms.validator.Length; 4 | import lombok.*; 5 | 6 | import javax.validation.constraints.Email; 7 | 8 | @NoArgsConstructor 9 | @Data 10 | public class UpdateInfoDTO { 11 | 12 | @Email(message = "{email}") 13 | private String email; 14 | 15 | @Length(min = 2, max = 10, message = "{user.nickname.size}") 16 | private String nickname; 17 | 18 | @Length(min = 2, max = 10, message = "{user.username.size}") 19 | private String username; 20 | 21 | @Length(max = 500, message = "{user.avatar.size}") 22 | private String avatar; 23 | } 24 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/dto/admin/DispatchPermissionDTO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.dto.admin; 2 | 3 | import lombok.*; 4 | 5 | import javax.validation.constraints.Min; 6 | import javax.validation.constraints.NotNull; 7 | 8 | @Data 9 | public class DispatchPermissionDTO { 10 | 11 | @Min(value = 1, message = "{admin.dispatch-permission.group-id.min}") 12 | @NotNull(message = "{admin.dispatch-permission.group-id.not-null}") 13 | private Long groupId; 14 | 15 | @Min(value = 1, message = "{admin.dispatch-permission.permission-id.min}") 16 | @NotNull(message = "{admin.dispatch-permission.permission-id.not-null}") 17 | private Long permissionId; 18 | } 19 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/mapper/GroupPermissionMapper.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.mapper; 2 | 3 | import com.lin.cms.demo.model.GroupPermissionDO; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author pedro 11 | * @since 2019-11-30 12 | */ 13 | public interface GroupPermissionMapper extends BaseMapper { 14 | 15 | int insertBatch(@Param("relations") List relations); 16 | 17 | int deleteBatchByGroupIdAndPermissionId(@Param("groupId") Long groupId, @Param("permissionIds") List permissionIds); 18 | } 19 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/vo/PageResponseVO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Data 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | @Builder 14 | public class PageResponseVO { 15 | 16 | private long total; 17 | 18 | private List items; 19 | 20 | private long page; 21 | 22 | private long count; 23 | 24 | public static PageResponseVO genPageResult(long total, List items, long page, long count) { 25 | return new PageResponseVO(total, items, page, count); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lin-cms-java 2 | 3 | 4 | ** 项目已经迁移,见 [lin-cms-spring-boot](https://github.com/TaleLin/lin-cms-spring-boot) ** 5 | 6 | > lin-cms 的 java 版实现基于 spring boot 和 mybatis-plus 7 | 8 | ## 设计哲学 9 | 10 | **简单,易用** 11 | 12 | ## 特性 13 | 14 | - [x] 优雅的权限系统 15 | - [x] 双令牌 16 | - [x] 骚气的banner 17 | - [x] 国际化 18 | - [x] 全局异常处理 19 | - [x] 优雅的分层结构 20 | - [x] 行为日志 21 | - [x] 请求日志 22 | - [x] 校验类库 23 | - [x] 文件上传 24 | 25 | ## 使用 26 | 27 | 在MySQL中新建数据库`lin-cms`,本地端口连接`3306` 28 | 29 | ```bash 30 | git clone https://github.com/PedroGao/lin-cms-java 31 | ``` 32 | 33 | ```bash 34 | cd lin-cms-java && mvn install 35 | ``` 36 | 37 | ```bash 38 | cd demo && java -jar target/demo-0.0.1-SNAPSHOT.jar 39 | ``` 40 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/bo/GroupPermissionsBO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.bo; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import com.lin.cms.demo.model.GroupDO; 5 | import com.lin.cms.demo.model.PermissionDO; 6 | import lombok.*; 7 | 8 | import java.util.List; 9 | 10 | @Data 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class GroupPermissionsBO { 14 | private Long id; 15 | 16 | private String name; 17 | 18 | private String info; 19 | 20 | private List permissions; 21 | 22 | public GroupPermissionsBO(GroupDO group, List permissions) { 23 | BeanUtil.copyProperties(group, this); 24 | this.permissions = permissions; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/response/Success.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.response; 2 | 3 | import com.lin.cms.beans.Code; 4 | import com.lin.cms.interfaces.BaseResponse; 5 | import lombok.Getter; 6 | import org.springframework.http.HttpStatus; 7 | 8 | public class Success implements BaseResponse { 9 | 10 | @Getter 11 | protected String message = Code.SUCCESS.getDescription(); 12 | 13 | @Getter 14 | protected int code = Code.SUCCESS.getCode(); 15 | 16 | @Getter 17 | protected int httpCode = HttpStatus.OK.value(); 18 | 19 | 20 | public Success(String message) { 21 | this.message = message; 22 | } 23 | 24 | public Success() { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/response/Created.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.response; 2 | 3 | import com.lin.cms.beans.Code; 4 | import com.lin.cms.interfaces.BaseResponse; 5 | import lombok.Getter; 6 | import org.springframework.http.HttpStatus; 7 | 8 | public class Created implements BaseResponse { 9 | 10 | @Getter 11 | protected String message = Code.CREATED.getDescription(); 12 | 13 | @Getter 14 | protected int code = Code.CREATED.getCode(); 15 | 16 | @Getter 17 | protected int httpCode = HttpStatus.CREATED.value(); 18 | 19 | 20 | public Created(String message) { 21 | this.message = message; 22 | } 23 | 24 | public Created() { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/validator/LongList.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.validator; 2 | 3 | import javax.validation.Payload; 4 | 5 | /** 6 | * 整型列表校验,校验 List 类型 7 | */ 8 | public @interface LongList { 9 | 10 | String message() default "Integer list cannot can't be blank"; 11 | 12 | /** 13 | * 每一个整数的最小值 14 | */ 15 | long min() default 0; 16 | 17 | /** 18 | * 每一个整数的最大值 19 | */ 20 | long max() default Long.MAX_VALUE; 21 | 22 | /** 23 | * 允许链表为 NULL 和 size == 0 24 | */ 25 | boolean allowBlank() default false; 26 | 27 | Class[] groups() default {}; 28 | 29 | Class[] payload() default {}; 30 | } 31 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/dto/admin/NewGroupDTO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.dto.admin; 2 | 3 | import com.lin.cms.validator.Length; 4 | import com.lin.cms.validator.LongList; 5 | import lombok.Data; 6 | 7 | import javax.validation.constraints.NotBlank; 8 | import java.util.List; 9 | 10 | @Data 11 | public class NewGroupDTO { 12 | @NotBlank(message = "{group.name.not-blank}") 13 | @Length(min = 1, max = 60, message = "{group.name.length}") 14 | private String name; 15 | 16 | @Length(min = 1, max = 255, message = "{group.info.length}") 17 | private String info; 18 | 19 | @LongList(allowBlank = true, message = "{permission.ids.long-list}") 20 | private List permissionIds; 21 | } 22 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/extensions/file/Uploader.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.extensions.file; 2 | 3 | import org.springframework.util.MultiValueMap; 4 | import org.springframework.web.multipart.MultipartFile; 5 | 6 | import java.util.List; 7 | 8 | public interface Uploader { 9 | 10 | /** 11 | * 上传文件 12 | * 13 | * @param fileMap 文件map 14 | * @return 文件数据 15 | */ 16 | List upload(MultiValueMap fileMap); 17 | 18 | /** 19 | * 上传文件 20 | * 21 | * @param fileMap 文件map 22 | * @param preHandler 预处理器 23 | * @return 文件数据 24 | */ 25 | List upload(MultiValueMap fileMap, PreHandler preHandler); 26 | } 27 | -------------------------------------------------------------------------------- /demo/src/main/resources/mapper/UserIdentityMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /core/src/main/java/com/lin/cms/core/utils/AnnotationUtil.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.core.utils; 2 | 3 | import com.lin.cms.core.annotation.Required; 4 | import com.lin.cms.core.enums.UserLevel; 5 | 6 | import java.lang.annotation.Annotation; 7 | 8 | public class AnnotationUtil { 9 | 10 | public static UserLevel findRequired(Annotation[] annotations) { 11 | for (Annotation annotation : annotations) { 12 | Class aClass = annotation.annotationType(); 13 | Required required = aClass.getAnnotation(Required.class); 14 | if (required != null) { 15 | return required.level(); 16 | } 17 | } 18 | return UserLevel.TOURIST; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /poem/src/main/java/com/lin/cms/plugins/poem/app/PoemDO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.plugins.poem.app; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableLogic; 4 | import com.baomidou.mybatisplus.annotation.TableName; 5 | import lombok.Data; 6 | 7 | import java.util.Date; 8 | 9 | @TableName(value = "poem") 10 | @Data 11 | public class PoemDO { 12 | private Integer id; 13 | 14 | private String title; 15 | 16 | private String author; 17 | 18 | private String dynasty; 19 | 20 | private String image; 21 | 22 | @TableLogic 23 | private Date deleteTime; 24 | 25 | /** 26 | * 创建时间 27 | */ 28 | private Date createTime; 29 | 30 | /** 31 | * 更新时间 32 | */ 33 | private Date updateTime; 34 | 35 | private String content; 36 | } -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/mapper/LogMapper.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.lin.cms.demo.common.mybatis.Page; 5 | import com.lin.cms.demo.model.LogDO; 6 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * @author pedro 12 | * @since 2019-11-30 13 | */ 14 | public interface LogMapper extends BaseMapper { 15 | 16 | IPage findLogsByUsernameAndRange(Page pager, String name, Date start, Date end); 17 | 18 | IPage searchLogsByUsernameAndKeywordAndRange(Page pager, String name, String keyword, Date start, Date end); 19 | 20 | IPage getUserNames(Page pager); 21 | } 22 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/common/LocalUser.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.common; 2 | 3 | import com.lin.cms.demo.model.UserDO; 4 | 5 | /** 6 | * 线程安全的当前登录用户,如果用户为登录,则得到 null 7 | */ 8 | public class LocalUser { 9 | 10 | private static ThreadLocal local = new ThreadLocal<>(); 11 | 12 | /** 13 | * 得到当前登录用户 14 | * 15 | * @return user | null 16 | */ 17 | public static UserDO getLocalUser() { 18 | return LocalUser.local.get(); 19 | } 20 | 21 | /** 22 | * 设置登录用户 23 | * 24 | * @param user user 25 | */ 26 | public static void setLocalUser(UserDO user) { 27 | LocalUser.local.set(user); 28 | } 29 | 30 | public static T getLocalUser(Class clazz) { 31 | return (T) local.get(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/validator/Enum.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.validator; 2 | 3 | import com.lin.cms.validator.impl.EnumValidator; 4 | 5 | import javax.validation.Constraint; 6 | import javax.validation.Payload; 7 | import java.lang.annotation.*; 8 | 9 | /** 10 | * 校验枚举,传入的值必须为target(枚举类)中的一项 11 | */ 12 | @Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Constraint(validatedBy = {EnumValidator.class}) 15 | @Documented 16 | public @interface Enum { 17 | 18 | String message() default "value must in enum"; 19 | 20 | boolean allowNull() default false; 21 | 22 | Class[] groups() default {}; 23 | 24 | Class[] payload() default {}; 25 | 26 | Class target(); 27 | } 28 | -------------------------------------------------------------------------------- /poem/src/test/java/auth/LoggerImpl.java: -------------------------------------------------------------------------------- 1 | package auth; 2 | 3 | import com.lin.cms.core.annotation.Logger; 4 | import com.lin.cms.core.annotation.RouteMeta; 5 | import com.lin.cms.interfaces.LoggerResolver; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | @Slf4j 13 | @Component 14 | public class LoggerImpl implements LoggerResolver { 15 | 16 | 17 | private static String REG_XP = "(?<=\\{)[^}]*(?=\\})"; 18 | 19 | 20 | @Override 21 | public void handle(RouteMeta meta, Logger logger, HttpServletRequest request, HttpServletResponse response) { 22 | // parse template and extract properties from request,response and modelAndView 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /demo/src/test/java/JsonTest.java: -------------------------------------------------------------------------------- 1 | import com.fasterxml.jackson.core.JsonProcessingException; 2 | import com.fasterxml.jackson.databind.ObjectMapper; 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.junit.Test; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | @Slf4j 11 | public class JsonTest { 12 | 13 | @Test 14 | public void bool() throws JsonProcessingException { 15 | Map input = new HashMap(); 16 | input.put("is", false); 17 | input.put("isPedro", "pedro"); 18 | ObjectMapper mapper = new ObjectMapper(); 19 | mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); 20 | String content = mapper.writeValueAsString(input); 21 | log.info(content); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/dto/admin/ResetPasswordDTO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.dto.admin; 2 | 3 | import com.lin.cms.validator.EqualField; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.Pattern; 10 | 11 | @EqualField(srcField = "newPassword", dstField = "confirmPassword", message = "{password.equal-field}") 12 | @Setter 13 | @Getter 14 | @NoArgsConstructor 15 | public class ResetPasswordDTO { 16 | 17 | @NotBlank(message = "{password.new-password.not-blank}") 18 | @Pattern(regexp = "^[A-Za-z0-9_*&$#@]{6,22}$", message = "{password.new-password.pattern}") 19 | private String newPassword; 20 | 21 | @NotBlank(message = "{password.confirm-password.not-blank}") 22 | private String confirmPassword; 23 | } 24 | -------------------------------------------------------------------------------- /demo/src/main/resources/mapper/UserGroupMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | INSERT INTO lin_user_group(user_id, group_id) 14 | VALUES 15 | 16 | (#{relation.userId}, #{relation.groupId}) 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /poem/src/test/java/MockApplication.java: -------------------------------------------------------------------------------- 1 | import org.mybatis.spring.annotation.MapperScan; 2 | import org.springframework.boot.SpringApplication; 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | import org.springframework.context.annotation.PropertySource; 5 | import org.springframework.context.annotation.PropertySources; 6 | 7 | /** 8 | * application.properties 9 | */ 10 | 11 | @SuppressWarnings("SpringBootApplicationSetup") 12 | @SpringBootApplication(scanBasePackages = {"com.lin.cms.plugins.poem", "auth"}) 13 | @MapperScan(basePackages = {"com.lin.cms.plugins.poem.app"}) 14 | @PropertySources({ 15 | @PropertySource("classpath:com/lin/cms/plugins/poem/plugin.properties") 16 | }) 17 | public class MockApplication { 18 | public static void main(String[] args) { 19 | SpringApplication.run(MockApplication.class, args); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/dto/book/CreateOrUpdateBookDTO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.dto.book; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | 7 | import javax.validation.constraints.*; 8 | 9 | @Setter 10 | @Getter 11 | @NoArgsConstructor 12 | public class CreateOrUpdateBookDTO { 13 | 14 | @NotEmpty(message = "{book.title.not-empty}") 15 | @Size(max = 50, message = "{book.title.size}") 16 | private String title; 17 | 18 | @NotEmpty(message = "{book.author.not-empty}") 19 | @Size(max = 50, message = "{book.author.size}") 20 | private String author; 21 | 22 | @NotEmpty(message = "{book.summary.not-empty}") 23 | @Size(max = 1000, message = "{book.summary.size}") 24 | private String summary; 25 | 26 | @Size(max = 100, message = "{book.image.size}") 27 | private String image; 28 | } 29 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/mapper/PermissionMapper.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.mapper; 2 | 3 | import com.lin.cms.demo.model.PermissionDO; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author pedro 11 | * @since 2019-11-30 12 | */ 13 | public interface PermissionMapper extends BaseMapper { 14 | 15 | /** 16 | * 通过分组ids得到所有分组下的权限 17 | * 18 | * @param groupIds 分组ids 19 | * @return 权限 20 | */ 21 | List selectPermissionsByGroupIds(@Param("groupIds") List groupIds); 22 | 23 | /** 24 | * 通过分组id得到所有分组下的权限 25 | * 26 | * @param groupId 分组id 27 | * @return 权限 28 | */ 29 | List selectPermissionsByGroupId(@Param("groupId") Long groupId); 30 | } 31 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/extensions/file/File.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.extensions.file; 2 | 3 | import lombok.*; 4 | 5 | @Setter 6 | @Getter 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Builder 10 | public class File { 11 | 12 | /** 13 | * 真实url 14 | */ 15 | private String url; 16 | 17 | /** 18 | * 前端上传的key 19 | */ 20 | private String key; 21 | 22 | /** 23 | * 若 local,表示文件路径 24 | */ 25 | private String path; 26 | 27 | /** 28 | * LOCAL REMOTE 29 | */ 30 | private String type; 31 | 32 | /** 33 | * 文件名称 34 | */ 35 | private String name; 36 | 37 | /** 38 | * 扩展名,例:.jpg 39 | */ 40 | private String extension; 41 | 42 | /** 43 | * 文件大小 44 | */ 45 | private Integer size; 46 | 47 | /** 48 | * md5值,防止上传重复文件 49 | */ 50 | private String md5; 51 | } 52 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/service/LogService.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.lin.cms.demo.model.LogDO; 5 | import com.baomidou.mybatisplus.extension.service.IService; 6 | 7 | import java.util.Date; 8 | 9 | /** 10 | * @author pedro 11 | * @since 2019-11-30 12 | */ 13 | public interface LogService extends IService { 14 | 15 | IPage getLogs(Long page, Long count, String name, Date start, Date end); 16 | 17 | IPage searchLogs(Long page, Long count, String name, String keyword, Date start, Date end); 18 | 19 | IPage getUserNames(Long page, Long count); 20 | 21 | boolean createLog(String message, String permission, Long userId, 22 | String username, String method, String path, 23 | Integer status); 24 | } 25 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/service/FileService.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.service; 2 | 3 | import com.lin.cms.demo.bo.FileBO; 4 | import com.lin.cms.demo.model.FileDO; 5 | import com.baomidou.mybatisplus.extension.service.IService; 6 | import org.springframework.util.MultiValueMap; 7 | import org.springframework.web.multipart.MultipartFile; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author pedro 13 | * @since 2019-11-30 14 | */ 15 | public interface FileService extends IService { 16 | 17 | /** 18 | * 上传文件 19 | * 20 | * @param fileMap 文件map 21 | * @return 文件数据 22 | */ 23 | List upload(MultiValueMap fileMap); 24 | 25 | /** 26 | * 通过md5检查文件是否存在 27 | * 28 | * @param md5 md5 29 | * @return true 表示已存在 30 | */ 31 | boolean checkFileExistByMd5(String md5); 32 | } 33 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/vo/UserPermissionsVO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.vo; 2 | 3 | import com.lin.cms.demo.model.UserDO; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import org.springframework.beans.BeanUtils; 7 | 8 | import java.util.List; 9 | 10 | @Setter 11 | @Getter 12 | public class UserPermissionsVO { 13 | 14 | private Long id; 15 | 16 | private String nickname; 17 | 18 | private String avatar; 19 | 20 | private Boolean admin; 21 | 22 | private String email; 23 | 24 | private List permissions; 25 | 26 | public UserPermissionsVO() { 27 | } 28 | 29 | public UserPermissionsVO(UserDO userDO, List permissions) { 30 | BeanUtils.copyProperties(userDO, this); 31 | this.permissions = permissions; 32 | } 33 | 34 | public UserPermissionsVO(UserDO userDO) { 35 | BeanUtils.copyProperties(userDO, this); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/model/BookDO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableLogic; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.fasterxml.jackson.annotation.JsonIgnore; 8 | import lombok.Data; 9 | 10 | import java.util.Date; 11 | 12 | @TableName("book") 13 | @Data 14 | public class BookDO { 15 | 16 | @TableId(value = "id", type = IdType.AUTO) 17 | private Long id; 18 | 19 | private String title; 20 | 21 | private String author; 22 | 23 | private String summary; 24 | 25 | private String image; 26 | 27 | @JsonIgnore 28 | private Date createTime; 29 | 30 | @JsonIgnore 31 | private Date updateTime; 32 | 33 | @JsonIgnore 34 | @TableLogic 35 | private Date deleteTime; 36 | } 37 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/exception/FailedException.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import com.lin.cms.beans.Code; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | public class FailedException extends HttpException { 8 | 9 | @Getter 10 | protected int code = Code.FAIL.getCode(); 11 | 12 | @Getter 13 | protected int httpCode = HttpStatus.INTERNAL_SERVER_ERROR.value(); 14 | 15 | public FailedException() { 16 | super(Code.FAIL.getDescription()); 17 | } 18 | 19 | public FailedException(String message) { 20 | super(message); 21 | } 22 | 23 | public FailedException(int code) { 24 | super(Code.FAIL.getDescription()); 25 | this.code = code; 26 | } 27 | 28 | public FailedException(String message, int code) { 29 | super(message); 30 | this.code = code; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/exception/RepeatException.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import com.lin.cms.beans.Code; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | public class RepeatException extends HttpException { 8 | 9 | @Getter 10 | protected int code = Code.REPEAT.getCode(); 11 | 12 | @Getter 13 | protected int httpCode = HttpStatus.BAD_REQUEST.value(); 14 | 15 | public RepeatException(String message) { 16 | super(message); 17 | } 18 | 19 | public RepeatException() { 20 | super(Code.REPEAT.getDescription()); 21 | } 22 | 23 | public RepeatException(int code) { 24 | super(Code.REPEAT.getDescription()); 25 | this.code = code; 26 | } 27 | 28 | public RepeatException(String message, int code) { 29 | super(message); 30 | this.code = code; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/validator/NotEmptyFields.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.validator; 2 | 3 | import com.lin.cms.validator.impl.NotEmptyFieldsValidator; 4 | 5 | import javax.validation.Constraint; 6 | import javax.validation.Payload; 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | /** 13 | * 校验List类型 14 | */ 15 | @Target(ElementType.FIELD) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Constraint(validatedBy = NotEmptyFieldsValidator.class) 18 | public @interface NotEmptyFields { 19 | 20 | String message() default "all must not be empty"; 21 | 22 | boolean allowNull() default false; 23 | 24 | boolean allowEmpty() default false; 25 | 26 | Class[] groups() default {}; 27 | 28 | Class[] payload() default {}; 29 | 30 | } -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test_eight: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up JDK 1.8 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 1.8 16 | - name: Build with Maven 17 | run: mvn test 18 | 19 | test_nine: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v1 25 | - name: Set up JDK 9 26 | uses: actions/setup-java@v1 27 | with: 28 | java-version: 9 29 | - name: Build with Maven 30 | run: mvn test 31 | 32 | test_eleven: 33 | 34 | runs-on: ubuntu-latest 35 | 36 | steps: 37 | - uses: actions/checkout@v1 38 | - name: Set up JDK 11 39 | uses: actions/setup-java@v1 40 | with: 41 | java-version: 11 42 | - name: Build with Maven 43 | run: mvn test 44 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/exception/NotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import com.lin.cms.beans.Code; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | public class NotFoundException extends HttpException { 8 | 9 | @Getter 10 | private int code = Code.NOT_FOUND.getCode(); 11 | 12 | @Getter 13 | private int httpCode = HttpStatus.NOT_FOUND.value(); 14 | 15 | public NotFoundException(String message) { 16 | super(message); 17 | } 18 | 19 | public NotFoundException(String message, int code) { 20 | super(message); 21 | this.code = code; 22 | } 23 | 24 | public NotFoundException(int code) { 25 | super(Code.NOT_FOUND.getDescription()); 26 | this.code = code; 27 | } 28 | 29 | public NotFoundException() { 30 | super(Code.NOT_FOUND.getDescription()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/exception/ForbiddenException.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import com.lin.cms.beans.Code; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | public class ForbiddenException extends HttpException { 8 | 9 | @Getter 10 | protected int code = Code.FORBIDDEN.getCode(); 11 | 12 | @Getter 13 | protected int httpCode = HttpStatus.FORBIDDEN.value(); 14 | 15 | public ForbiddenException(String message) { 16 | super(message); 17 | } 18 | 19 | public ForbiddenException(String message, int code) { 20 | super(message); 21 | this.code = code; 22 | } 23 | 24 | public ForbiddenException(int code) { 25 | super(Code.FORBIDDEN.getDescription()); 26 | this.code = code; 27 | } 28 | 29 | public ForbiddenException() { 30 | super(Code.FORBIDDEN.getDescription()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/dto/user/ChangePasswordDTO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.dto.user; 2 | 3 | import com.lin.cms.validator.EqualField; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.Pattern; 10 | 11 | @Setter 12 | @Getter 13 | @NoArgsConstructor 14 | @EqualField(srcField = "newPassword", dstField = "confirmPassword", message = "{password.equal-field}") 15 | public class ChangePasswordDTO { 16 | 17 | @NotBlank(message = "{password.new-password.not-blank}") 18 | @Pattern(regexp = "^[A-Za-z0-9_*&$#@]{6,22}$", message = "{password.new-password.pattern}") 19 | private String newPassword; 20 | 21 | @NotBlank(message = "{password.confirm-password.not-blank}") 22 | private String confirmPassword; 23 | 24 | @NotBlank(message = "{password.old-password.not-blank}") 25 | private String oldPassword; 26 | } 27 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/model/UserGroupDO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.Data; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * @author pedro 12 | * @since 2019-11-30 13 | */ 14 | @TableName("lin_user_group") 15 | @Data 16 | public class UserGroupDO implements Serializable { 17 | 18 | private static final long serialVersionUID = 1L; 19 | 20 | @TableId(value = "id", type = IdType.AUTO) 21 | private Long id; 22 | 23 | /** 24 | * 用户id 25 | */ 26 | private Long userId; 27 | 28 | /** 29 | * 分组id 30 | */ 31 | private Long groupId; 32 | 33 | public UserGroupDO(Long userId, Long groupId) { 34 | this.userId = userId; 35 | this.groupId = groupId; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /demo/src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | server.port=5000 2 | # \u5F00\u53D1\u73AF\u5883\u914D\u7F6E 3 | # \u6570\u636E\u6E90\u914D\u7F6E\uFF0C\u8BF7\u4FEE\u6539\u4E3A\u4F60\u9879\u76EE\u7684\u5B9E\u9645\u914D\u7F6E 4 | spring.datasource.url=jdbc:mysql://localhost:3306/lin-cms?useSSL=false&serverTimezone=UTC&characterEncoding=UTF8 5 | spring.datasource.username=root 6 | spring.datasource.password=123456 7 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 8 | # 9 | # mybatis logging 10 | # \u4E0D\u8981\u4F7F\u7528\uFF0C\u548C logback \u51B2\u7A81 11 | # mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl 12 | # 13 | # \u9759\u6001\u8D44\u6E90\u7684\u670D\u52A1 14 | # 15 | # \u65E5\u5FD7\u8BB0\u5F55\u95EE\u9898 16 | # 17 | logging.level.com.lin.cms.demo.mapper=debug 18 | logging.level.web=debug 19 | logging.config=classpath:logback-spring.xml 20 | # 21 | # \u5F00\u542F\u6743\u9650\u62E6\u622A 22 | auth.enabled=true 23 | request-log.enabled=true -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/exception/TokenExpiredException.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import com.lin.cms.beans.Code; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | public class TokenExpiredException extends HttpException { 8 | 9 | @Getter 10 | protected int code = Code.TOKEN_EXPIRED.getCode(); 11 | 12 | @Getter 13 | protected int httpCode = HttpStatus.UNAUTHORIZED.value(); 14 | 15 | public TokenExpiredException(String message) { 16 | super(message); 17 | } 18 | 19 | public TokenExpiredException() { 20 | super(Code.TOKEN_EXPIRED.getDescription()); 21 | } 22 | 23 | public TokenExpiredException(int code) { 24 | super(Code.TOKEN_EXPIRED.getDescription()); 25 | this.code = code; 26 | } 27 | 28 | public TokenExpiredException(String message, int code) { 29 | super(message); 30 | this.code = code; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/exception/TokenInvalidException.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import com.lin.cms.beans.Code; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | public class TokenInvalidException extends HttpException { 8 | 9 | @Getter 10 | protected int code = Code.TOKEN_INVALID.getCode(); 11 | 12 | @Getter 13 | protected int httpCode = HttpStatus.UNAUTHORIZED.value(); 14 | 15 | public TokenInvalidException(String message) { 16 | super(message); 17 | } 18 | 19 | public TokenInvalidException() { 20 | super(Code.TOKEN_INVALID.getDescription()); 21 | } 22 | 23 | public TokenInvalidException(int code) { 24 | super(Code.TOKEN_INVALID.getDescription()); 25 | this.code = code; 26 | } 27 | 28 | public TokenInvalidException(String message, int code) { 29 | super(message); 30 | this.code = code; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/common/configure/CodeConfig.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.common.configure; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.context.annotation.PropertySource; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | @Component 11 | @ConfigurationProperties(prefix = "lin.cms") 12 | @PropertySource(value = "classpath:code.properties", encoding = "UTF-8") 13 | public class CodeConfig { 14 | 15 | private static Map codeMessage = new HashMap<>(); 16 | 17 | public static String getMessage(Integer code) { 18 | return codeMessage.get(code); 19 | } 20 | 21 | public Map getCodeMessage() { 22 | return codeMessage; 23 | } 24 | 25 | public void setCodeMessage(Map codeMessage) { 26 | CodeConfig.codeMessage = codeMessage; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/exception/RequestLimitException.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import com.lin.cms.beans.Code; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | public class RequestLimitException extends HttpException { 8 | 9 | @Getter 10 | protected int code = Code.REQUEST_LIMIT.getCode(); 11 | 12 | @Getter 13 | protected int httpCode = HttpStatus.TOO_MANY_REQUESTS.value(); 14 | 15 | public RequestLimitException(String message) { 16 | super(message); 17 | } 18 | 19 | public RequestLimitException() { 20 | super(Code.REQUEST_LIMIT.getDescription()); 21 | } 22 | 23 | public RequestLimitException(int code) { 24 | super(Code.REQUEST_LIMIT.getDescription()); 25 | this.code = code; 26 | } 27 | 28 | public RequestLimitException(String message, int code) { 29 | super(message); 30 | this.code = code; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/exception/FileExtensionException.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import com.lin.cms.beans.Code; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | public class FileExtensionException extends HttpException { 8 | 9 | @Getter 10 | protected int code = Code.FILE_EXTENSION.getCode(); 11 | 12 | @Getter 13 | protected int httpCode = HttpStatus.NOT_ACCEPTABLE.value(); 14 | 15 | public FileExtensionException(String message) { 16 | super(message); 17 | } 18 | 19 | public FileExtensionException() { 20 | super(Code.FILE_EXTENSION.getDescription()); 21 | } 22 | 23 | public FileExtensionException(int code) { 24 | super(Code.FILE_EXTENSION.getDescription()); 25 | this.code = code; 26 | } 27 | 28 | public FileExtensionException(String message, int code) { 29 | super(message); 30 | this.code = code; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/exception/FileTooManyException.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import com.lin.cms.beans.Code; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | public class FileTooManyException extends HttpException { 8 | 9 | @Getter 10 | protected int code = Code.FILE_TOO_MANY.getCode(); 11 | 12 | @Getter 13 | protected int httpCode = HttpStatus.PAYLOAD_TOO_LARGE.value(); 14 | 15 | 16 | public FileTooManyException(String message) { 17 | super(message); 18 | } 19 | 20 | public FileTooManyException() { 21 | super(Code.FILE_TOO_MANY.getDescription()); 22 | } 23 | 24 | public FileTooManyException(int code) { 25 | super(Code.FILE_TOO_MANY.getDescription()); 26 | this.code = code; 27 | } 28 | 29 | 30 | public FileTooManyException(String message, int code) { 31 | super(message); 32 | this.code = code; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/exception/RefreshFailedException.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import com.lin.cms.beans.Code; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | public class RefreshFailedException extends HttpException { 8 | 9 | @Getter 10 | protected int code = Code.REFRESH_FAILED.getCode(); 11 | 12 | @Getter 13 | protected int httpCode = HttpStatus.UNAUTHORIZED.value(); 14 | 15 | public RefreshFailedException(String message) { 16 | super(message); 17 | } 18 | 19 | public RefreshFailedException() { 20 | super(Code.REFRESH_FAILED.getDescription()); 21 | } 22 | 23 | public RefreshFailedException(int code) { 24 | super(Code.REFRESH_FAILED.getDescription()); 25 | this.code = code; 26 | } 27 | 28 | public RefreshFailedException(String message, int code) { 29 | super(message); 30 | this.code = code; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/exception/FileTooLargeException.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import com.lin.cms.beans.Code; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | 8 | public class FileTooLargeException extends HttpException { 9 | 10 | @Getter 11 | protected int code = Code.FILE_TOO_LARGE.getCode(); 12 | 13 | @Getter 14 | protected int httpCode = HttpStatus.PAYLOAD_TOO_LARGE.value(); 15 | 16 | 17 | public FileTooLargeException(String message) { 18 | super(message); 19 | } 20 | 21 | public FileTooLargeException() { 22 | super(Code.FILE_TOO_LARGE.getDescription()); 23 | } 24 | 25 | public FileTooLargeException(int code) { 26 | super(Code.FILE_TOO_LARGE.getDescription()); 27 | this.code = code; 28 | } 29 | 30 | public FileTooLargeException(String message, int code) { 31 | super(message); 32 | this.code = code; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/validator/EqualField.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.validator; 2 | 3 | import com.lin.cms.validator.impl.EqualFieldValidator; 4 | 5 | import javax.validation.Constraint; 6 | import javax.validation.Payload; 7 | import java.lang.annotation.Documented; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | 11 | import static java.lang.annotation.ElementType.*; 12 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 13 | 14 | 15 | @Target({TYPE, METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) 16 | @Retention(RUNTIME) 17 | @Documented 18 | @Constraint(validatedBy = {EqualFieldValidator.class}) 19 | public @interface EqualField { 20 | 21 | String srcField() default ""; 22 | 23 | String dstField() default ""; 24 | 25 | String message() default "the two fields must be equal"; 26 | 27 | Class[] groups() default {}; 28 | 29 | Class[] payload() default {}; 30 | } 31 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/vo/UserInfoVO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.vo; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import com.lin.cms.demo.model.UserDO; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.util.List; 11 | 12 | @Data 13 | @Builder 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class UserInfoVO { 17 | 18 | private Long id; 19 | 20 | /** 21 | * 用户名,唯一 22 | */ 23 | private String username; 24 | 25 | /** 26 | * 用户昵称 27 | */ 28 | private String nickname; 29 | 30 | /** 31 | * 头像url 32 | */ 33 | private String avatar; 34 | 35 | /** 36 | * 邮箱 37 | */ 38 | private String email; 39 | 40 | /** 41 | * 分组 42 | */ 43 | private List groups; 44 | 45 | public UserInfoVO(UserDO user, List groups) { 46 | BeanUtil.copyProperties(user, this); 47 | this.groups = groups; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/exception/MethodNotAllowedException.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import com.lin.cms.beans.Code; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | public class MethodNotAllowedException extends HttpException { 8 | 9 | @Getter 10 | protected int code = Code.METHOD_NOT_ALLOWED.getCode(); 11 | 12 | @Getter 13 | protected int httpCode = HttpStatus.METHOD_NOT_ALLOWED.value(); 14 | 15 | public MethodNotAllowedException(String message) { 16 | super(message); 17 | } 18 | 19 | public MethodNotAllowedException() { 20 | super(Code.METHOD_NOT_ALLOWED.getDescription()); 21 | } 22 | 23 | public MethodNotAllowedException(int code) { 24 | super(Code.METHOD_NOT_ALLOWED.getDescription()); 25 | this.code = code; 26 | } 27 | 28 | public MethodNotAllowedException(String message, int code) { 29 | super(message); 30 | this.code = code; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.lin.cms.demo.common.mybatis.Page; 5 | import com.lin.cms.demo.model.UserDO; 6 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 7 | 8 | 9 | /** 10 | * @author pedro 11 | * @since 2019-12-02 12 | */ 13 | public interface UserMapper extends BaseMapper { 14 | 15 | /** 16 | * 查询用户名为$username的人数 17 | * 18 | * @param username 用户名 19 | * @return 人数 20 | */ 21 | int selectCountByUsername(String username); 22 | 23 | /** 24 | * 查询用户id为$id的人数 25 | * 26 | * @param id 用户id 27 | * @return 人数 28 | */ 29 | int selectCountById(Long id); 30 | 31 | /** 32 | * 通过分组id分页获取用户数据 33 | * 34 | * @param pager 分页 35 | * @param groupId 分组id 36 | * @return 分页数据 37 | */ 38 | IPage selectPageByGroupId(Page pager, Long groupId); 39 | } 40 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/exception/AuthenticationException.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import com.lin.cms.beans.Code; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | /** 8 | * 授权异常 9 | */ 10 | public class AuthenticationException extends HttpException { 11 | 12 | @Getter 13 | protected int httpCode = HttpStatus.UNAUTHORIZED.value(); 14 | 15 | @Getter 16 | protected int code = Code.UN_AUTHENTICATION.getCode(); 17 | 18 | public AuthenticationException() { 19 | super(Code.UN_AUTHENTICATION.getDescription()); 20 | } 21 | 22 | public AuthenticationException(int code) { 23 | super(Code.UN_AUTHENTICATION.getDescription()); 24 | this.code = code; 25 | } 26 | 27 | public AuthenticationException(String message) { 28 | super(message); 29 | } 30 | 31 | public AuthenticationException(String message, int code) { 32 | super(message); 33 | this.code = code; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/exception/AuthorizationException.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import com.lin.cms.beans.Code; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | 8 | /** 9 | * 授权异常 10 | */ 11 | public class AuthorizationException extends HttpException { 12 | 13 | @Getter 14 | protected int httpCode = HttpStatus.UNAUTHORIZED.value(); 15 | 16 | @Getter 17 | protected int code = Code.UN_AUTHORIZATION.getCode(); 18 | 19 | public AuthorizationException() { 20 | super(Code.UN_AUTHORIZATION.getDescription()); 21 | } 22 | 23 | public AuthorizationException(String message) { 24 | super(message); 25 | } 26 | 27 | public AuthorizationException(int code) { 28 | super(Code.UN_AUTHORIZATION.getDescription()); 29 | this.code = code; 30 | } 31 | 32 | public AuthorizationException(String message, int code) { 33 | super(message); 34 | this.code = code; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/utils/RequestUtil.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.utils; 2 | 3 | import org.springframework.web.context.request.RequestContextHolder; 4 | import org.springframework.web.context.request.ServletRequestAttributes; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | 8 | public class RequestUtil { 9 | 10 | public static HttpServletRequest getRequest() { 11 | return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 12 | } 13 | 14 | public static String getRequestUrl() { 15 | return getRequest().getServletPath(); 16 | } 17 | 18 | public static String getSimpleRequest(HttpServletRequest request) { 19 | return String.format("%s %s", request.getMethod(), request.getServletPath()); 20 | } 21 | 22 | public static String getSimpleRequest() { 23 | HttpServletRequest request = getRequest(); 24 | return String.format("%s %s", request.getMethod(), request.getServletPath()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/model/GroupPermissionDO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.io.Serializable; 11 | 12 | /** 13 | * @author pedro 14 | * @since 2019-11-30 15 | */ 16 | @TableName("lin_group_permission") 17 | @Data 18 | public class GroupPermissionDO implements Serializable { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | @TableId(value = "id", type = IdType.AUTO) 23 | private Long id; 24 | 25 | /** 26 | * 分组id 27 | */ 28 | private Long groupId; 29 | 30 | /** 31 | * 权限id 32 | */ 33 | private Long permissionId; 34 | 35 | public GroupPermissionDO(Long groupId, Long permissionId) { 36 | this.groupId = groupId; 37 | this.permissionId = permissionId; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /core/src/main/java/com/lin/cms/core/utils/EncryptUtil.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.core.utils; 2 | 3 | import com.amdelamar.jhash.Hash; 4 | import com.amdelamar.jhash.algorithms.Type; 5 | import com.amdelamar.jhash.exception.InvalidHashException; 6 | 7 | public class EncryptUtil { 8 | 9 | /** 10 | * 设置密文密码 11 | * 12 | * @param password 原始密码 13 | * @return 加密密码 14 | */ 15 | public static String encrypt(String password) { 16 | char[] chars = password.toCharArray(); 17 | return Hash.password(chars).algorithm(Type.PBKDF2_SHA256).create(); 18 | } 19 | 20 | /** 21 | * 验证加密密码 22 | * 23 | * @param encryptedPassword 密文密码 24 | * @param plainPassword 明文密码 25 | * @return 验证是否成功 26 | */ 27 | public static boolean verify(String encryptedPassword, String plainPassword) { 28 | char[] chars = plainPassword.toCharArray(); 29 | try { 30 | return Hash.password(chars).algorithm(Type.PBKDF2_SHA256).verify(encryptedPassword); 31 | } catch (InvalidHashException e) { 32 | return false; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 pedro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.lin 8 | cms 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | core 13 | lin-cms-spring-boot-autoconfigure 14 | lin-cms-spring-boot-starter 15 | demo 16 | 17 | 18 | pom 19 | 20 | lin-cms 21 | lin-cms 简单好用的后台管理CMS 22 | 23 | 24 | 25 | The MIT License (MIT) 26 | 27 | 28 | 29 | 30 | 31 | pedro 32 | pedrogao1996@gmail.com 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/validator/impl/NotEmptyFieldsValidator.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.validator.impl; 2 | 3 | import com.lin.cms.validator.NotEmptyFields; 4 | 5 | import javax.validation.ConstraintValidator; 6 | import javax.validation.ConstraintValidatorContext; 7 | import java.util.List; 8 | 9 | public class NotEmptyFieldsValidator implements ConstraintValidator> { 10 | 11 | private boolean allowNull; 12 | 13 | private boolean allowEmpty; 14 | 15 | @Override 16 | public void initialize(NotEmptyFields constraintAnnotation) { 17 | this.allowNull = constraintAnnotation.allowNull(); 18 | this.allowEmpty = constraintAnnotation.allowEmpty(); 19 | } 20 | 21 | @Override 22 | public boolean isValid(List objects, ConstraintValidatorContext context) { 23 | if (allowNull && objects == null) { 24 | return true; 25 | } 26 | if (allowEmpty && objects.size() == 0) { 27 | return true; 28 | } 29 | return objects.stream().allMatch(nef -> nef != null && !nef.trim().isEmpty()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/controller/cms/FileController.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.controller.cms; 2 | 3 | import com.lin.cms.demo.bo.FileBO; 4 | import com.lin.cms.demo.service.FileService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.util.MultiValueMap; 7 | import org.springframework.web.bind.annotation.*; 8 | import org.springframework.web.multipart.MultipartFile; 9 | import org.springframework.web.multipart.MultipartHttpServletRequest; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import java.util.List; 13 | 14 | @RestController 15 | @RequestMapping("/cms/file") 16 | public class FileController { 17 | 18 | @Autowired 19 | private FileService fileService; 20 | 21 | @PostMapping("") 22 | public List upload(HttpServletRequest request) { 23 | MultipartHttpServletRequest multipartHttpServletRequest = ((MultipartHttpServletRequest) request); 24 | MultiValueMap fileMap = multipartHttpServletRequest.getMultiFileMap(); 25 | List files = fileService.upload(fileMap); 26 | return files; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/model/GroupDO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.io.Serializable; 11 | import java.util.Date; 12 | 13 | /** 14 | * @author pedro 15 | * @since 2019-11-30 16 | */ 17 | @Data 18 | @Builder 19 | @TableName("lin_group") 20 | @NoArgsConstructor 21 | @AllArgsConstructor 22 | public class GroupDO implements Serializable { 23 | 24 | private static final long serialVersionUID = 1L; 25 | 26 | @TableId(value = "id", type = IdType.AUTO) 27 | private Long id; 28 | 29 | /** 30 | * 分组名称,例如:搬砖者 31 | */ 32 | private String name; 33 | 34 | /** 35 | * 分组信息:例如:搬砖的人 36 | */ 37 | private String info; 38 | 39 | @JsonIgnore 40 | private Date createTime; 41 | 42 | @JsonIgnore 43 | private Date updateTime; 44 | 45 | @JsonIgnore 46 | @TableLogic 47 | private Date deleteTime; 48 | } 49 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/validator/impl/LongListValidator.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.validator.impl; 2 | 3 | import com.lin.cms.validator.LongList; 4 | 5 | import javax.validation.ConstraintValidator; 6 | import javax.validation.ConstraintValidatorContext; 7 | import java.util.List; 8 | 9 | public class LongListValidator implements ConstraintValidator> { 10 | 11 | private long min; 12 | 13 | private long max; 14 | 15 | private boolean allowBlank; 16 | 17 | @Override 18 | public void initialize(LongList constraintAnnotation) { 19 | this.max = constraintAnnotation.max(); 20 | this.min = constraintAnnotation.min(); 21 | this.allowBlank = constraintAnnotation.allowBlank(); 22 | } 23 | 24 | @Override 25 | public boolean isValid(List value, ConstraintValidatorContext context) { 26 | if ((value == null || value.isEmpty())) { 27 | return allowBlank; 28 | } 29 | for (Long o : value) { 30 | if (o < min || o > max) { 31 | return false; 32 | } 33 | } 34 | return true; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/plugin.md: -------------------------------------------------------------------------------- 1 | # 插件(plugin) 2 | 3 | > 究竟什么是插件?插件又哪几种?或者说哪几种粒度? 4 | > 究竟该以何种方式提供? 5 | 6 | ## 插件开发 7 | 8 | 以 java 为例,某种意义上插件必须得独立于主工程进行开发。因此必须提供插件可以独立开发的环境 9 | 10 | 这个环境由 `starter` 来提供,`starter` 暂定支持两种环境,一种为 *主工程* 环境 11 | 12 | 另一种为 *插件* 环境。 13 | 14 | 主工程环境不做过多的赘述,关键是*插件*环境的提炼,插件环境必须能够让插件单独出来足够可以开发。 15 | 16 | 这里可以提供两种思路: 17 | 18 | 1. 模拟出一个 app 环境供插件进行开发,但是开发后被主应用加载后`可能会存在路由变化的情况`, 19 | 但这很明显可以接受。但是其它的地方,诸如配置,类库皆可以被完整保留。 20 | 21 | 2. 将默认的 app 置于 starter 中,通过开发环境,如 `plugin`,`app`环境下的不同来提供不同的 22 | 开发环境。 23 | 24 | ## 插件类型 25 | 26 | 以 `业务` 这个东西来区分插件,暂时将插件分为两类: 27 | 28 | - 普通插件:含基础类库的插件,如 uploader 之类的,含类库,配置及其它,被装载到依赖库中,主动导入 29 | 进行使用,如需更改可通过继承的方式进行定制。细粒度的插件 30 | 31 | - 业务插件:含业务(服务接口),可通过 http 方式进行访问的插件,含基础类库,配置,业务层,校验层及其它, 32 | 会将它发布在 github 仓库上,不发布在 maven repository 或者 npm 上。供 clone 下来进行业务修改和 33 | 二次开发。 34 | 35 | 当然,也可以根据其它的粒度层次来进行划分。 36 | 37 | ## 插件实现 38 | 39 | 暂时的想法,通过 `hook` 来实现,spring boot 有 listener,作用一致,叫法不同。 40 | 41 | 通过 `hook`,调用插件的加载。 42 | 43 | 待解决的问题: 44 | 45 | 1. 如何让插件的开发可以享用权限的控制 46 | 2. 如何让插件享受令牌的使用 47 | 48 | -> 或许还是得需要将这些抽离到 `starter` 里面 -> 不可取 49 | 50 | 将逻辑仍然丢在核心库里面,将业务逻辑单独抽离出接口,让 demo 为其实现 51 | 52 | 目前可以让插件使用权限和行为日志等诸多的功能,但是其实现与主工程不同,不能够完全模拟,只能够在逻辑上保持一致 -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/interfaces/AuthorizeVerifyResolver.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.interfaces; 2 | 3 | import com.lin.cms.core.annotation.RouteMeta; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | 8 | public interface AuthorizeVerifyResolver { 9 | 10 | /** 11 | * 处理 LoginRequired的情况 12 | */ 13 | boolean handleLogin(HttpServletRequest request, HttpServletResponse response, RouteMeta meta); 14 | 15 | /** 16 | * 处理 GroupRequired的情况 17 | */ 18 | boolean handleGroup(HttpServletRequest request, HttpServletResponse response, RouteMeta meta); 19 | 20 | /** 21 | * 处理 AdminRequired的情况 22 | */ 23 | boolean handleAdmin(HttpServletRequest request, HttpServletResponse response, RouteMeta meta); 24 | 25 | /** 26 | * 处理 RefreshRequired的情况 27 | */ 28 | boolean handleRefresh(HttpServletRequest request, HttpServletResponse response, RouteMeta meta); 29 | 30 | /** 31 | * 处理 当前的handler 不是 HandlerMethod 的情况 32 | */ 33 | boolean handleNotHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler); 34 | } 35 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/validator/impl/LengthValidator.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.validator.impl; 2 | 3 | import com.lin.cms.validator.Length; 4 | import org.apache.logging.log4j.util.Strings; 5 | 6 | import javax.validation.ConstraintValidator; 7 | import javax.validation.ConstraintValidatorContext; 8 | 9 | public class LengthValidator implements ConstraintValidator { 10 | 11 | private boolean allowBlank; 12 | 13 | private int min; 14 | 15 | private int max; 16 | 17 | @Override 18 | public void initialize(Length constraintAnnotation) { 19 | this.allowBlank = constraintAnnotation.allowBlank(); 20 | this.max = constraintAnnotation.max(); 21 | this.min = constraintAnnotation.min(); 22 | } 23 | 24 | @Override 25 | public boolean isValid(String value, ConstraintValidatorContext context) { 26 | if (allowBlank) { 27 | if (Strings.isBlank(value)) { 28 | return true; 29 | } 30 | } else { 31 | if (value.length() >= min && value.length() <= max) { 32 | return true; 33 | } 34 | } 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/model/PermissionDO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableLogic; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.fasterxml.jackson.annotation.JsonIgnore; 8 | import lombok.Builder; 9 | import lombok.Data; 10 | 11 | import java.io.Serializable; 12 | import java.util.Date; 13 | 14 | /** 15 | * @author pedro 16 | * @since 2019-11-30 17 | */ 18 | @Data 19 | @Builder 20 | @TableName("lin_permission") 21 | public class PermissionDO implements Serializable { 22 | 23 | private static final long serialVersionUID = 1L; 24 | 25 | @TableId(value = "id", type = IdType.AUTO) 26 | private Long id; 27 | 28 | /** 29 | * 权限名称,例如:访问首页 30 | */ 31 | private String name; 32 | 33 | /** 34 | * 权限所属模块,例如:人员管理 35 | */ 36 | private String module; 37 | 38 | @JsonIgnore 39 | private Date createTime; 40 | 41 | @JsonIgnore 42 | private Date updateTime; 43 | 44 | @JsonIgnore 45 | @TableLogic 46 | private Date deleteTime; 47 | } 48 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/mapper/GroupMapper.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.mapper; 2 | 3 | import com.lin.cms.demo.model.GroupDO; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author pedro 11 | * @since 2019-11-30 12 | */ 13 | public interface GroupMapper extends BaseMapper { 14 | 15 | /** 16 | * 获得用户的所有分组 17 | * 18 | * @param userId 用户id 19 | * @return 所有分组 20 | */ 21 | List selectUserGroups(@Param("userId") Long userId); 22 | 23 | /** 24 | * 获得用户的所有分组id 25 | * 26 | * @param userId 用户id 27 | * @return 所有分组id 28 | */ 29 | List selectUserGroupIds(@Param("userId") Long userId); 30 | 31 | /** 32 | * 通过id获得分组个数 33 | * 34 | * @param id id 35 | * @return 个数 36 | */ 37 | int selectCountById(@Param("id") Long id); 38 | 39 | /** 40 | * 检查用户是否在该名称的分组里 41 | * 42 | * @param userId 用户id 43 | * @param groupName 分组名 44 | */ 45 | int selectCountUserByUserIdAndGroupName(@Param("userId") Long userId, @Param("groupName") String groupName); 46 | } 47 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/dto/user/RegisterDTO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.dto.user; 2 | 3 | import com.lin.cms.validator.EqualField; 4 | import com.lin.cms.validator.LongList; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | import javax.validation.constraints.*; 10 | import java.util.List; 11 | 12 | @Setter 13 | @Getter 14 | @NoArgsConstructor 15 | @EqualField(srcField = "password", dstField = "confirmPassword", message = "{password.equal-field}") 16 | public class RegisterDTO { 17 | 18 | @NotBlank(message = "{user.register.username.not-blank}") 19 | @Size(min = 2, max = 10, message = "{user.register.username.size}") 20 | private String username; 21 | 22 | @LongList(allowBlank = true, message = "{user.register.group-ids.long-list}") 23 | private List groupIds; 24 | 25 | @Email(message = "{email}") 26 | private String email; 27 | 28 | @NotBlank(message = "{password.new-password.not-blank}") 29 | @Pattern(regexp = "^[A-Za-z0-9_*&$#@]{6,22}$", message = "{password.new-password.pattern}") 30 | private String password; 31 | 32 | @NotBlank(message = "{password.confirm-password.not-blank}") 33 | private String confirmPassword; 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /poem/src/test/java/auth/AuthVerifyResolverImpl.java: -------------------------------------------------------------------------------- 1 | package auth; 2 | 3 | import com.lin.cms.core.annotation.RouteMeta; 4 | import com.lin.cms.interfaces.AuthorizeVerifyResolver; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | @Component 11 | public class AuthVerifyResolverImpl implements AuthorizeVerifyResolver { 12 | 13 | @Override 14 | public boolean handleLogin(HttpServletRequest request, HttpServletResponse response, RouteMeta meta) { 15 | return false; 16 | } 17 | 18 | @Override 19 | public boolean handleGroup(HttpServletRequest request, HttpServletResponse response, RouteMeta meta) { 20 | return false; 21 | } 22 | 23 | @Override 24 | public boolean handleAdmin(HttpServletRequest request, HttpServletResponse response, RouteMeta meta) { 25 | return false; 26 | } 27 | 28 | @Override 29 | public boolean handleRefresh(HttpServletRequest request, HttpServletResponse response, RouteMeta meta) { 30 | return false; 31 | } 32 | 33 | @Override 34 | public boolean handleNotHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler) { 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/common/aop/ResultAspect.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.common.aop; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import com.lin.cms.demo.common.configure.CodeConfig; 5 | import com.lin.cms.demo.vo.UnifyResponseVO; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.aspectj.lang.annotation.AfterReturning; 8 | import org.aspectj.lang.annotation.Aspect; 9 | import org.aspectj.lang.annotation.Pointcut; 10 | import org.springframework.stereotype.Component; 11 | 12 | 13 | /** 14 | * 处理返回结果为 Result 的视图函数 15 | * 默认的返回均是英文,在此处通过error-code替换成中文 16 | */ 17 | @Aspect 18 | @Component 19 | @Slf4j 20 | public class ResultAspect { 21 | 22 | 23 | @Pointcut("execution(public * com.lin.cms.demo.controller..*.*(..))") 24 | public void handlePlaceholder() { 25 | } 26 | 27 | @AfterReturning(returning = "ret", pointcut = "handlePlaceholder()") 28 | public void doAfterReturning(Object ret) throws Throwable { 29 | if (ret instanceof UnifyResponseVO) { 30 | UnifyResponseVO result = (UnifyResponseVO) ret; 31 | int code = result.getCode(); 32 | String message = CodeConfig.getMessage(code); 33 | if (StrUtil.isNotBlank(message)) { 34 | result.setMessage(message); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/common/interceptor/RequestLogInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.common.interceptor; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | @Slf4j 10 | public class RequestLogInterceptor extends HandlerInterceptorAdapter { 11 | 12 | 13 | private ThreadLocal startTime = new ThreadLocal<>(); 14 | 15 | @Override 16 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 17 | startTime.set(System.currentTimeMillis()); 18 | return super.preHandle(request, response, handler); 19 | } 20 | 21 | @Override 22 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 23 | super.afterCompletion(request, response, handler, ex); 24 | log.info("[{}] -> [{}] from: {} costs: {}ms", 25 | request.getMethod(), 26 | request.getServletPath(), 27 | request.getRemoteAddr(), 28 | System.currentTimeMillis() - startTime.get() 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /demo/src/main/resources/mapper/GroupPermissionMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | INSERT INTO lin_group_permission(group_id, permission_id) 15 | VALUES 16 | 17 | (#{relation.groupId}, #{relation.permissionId}) 18 | 19 | 20 | 21 | 22 | DELETE FROM lin_group_permission 23 | WHERE group_id = #{groupId} 24 | AND permission_id IN 25 | 26 | #{permissionId} 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/model/FileDO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableLogic; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.fasterxml.jackson.annotation.JsonIgnore; 8 | import lombok.Data; 9 | 10 | import java.io.Serializable; 11 | import java.util.Date; 12 | 13 | /** 14 | * @author pedro 15 | * @since 2019-11-30 16 | */ 17 | @Data 18 | @TableName("lin_file") 19 | public class FileDO implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | @TableId(value = "id", type = IdType.AUTO) 24 | private Long id; 25 | 26 | private String path; 27 | 28 | /** 29 | * LOCAL REMOTE 30 | */ 31 | private String type; 32 | 33 | private String name; 34 | 35 | private String extension; 36 | 37 | private Integer size; 38 | 39 | /** 40 | * md5值,防止上传重复文件 41 | */ 42 | private String md5; 43 | 44 | @JsonIgnore 45 | private Date createTime; 46 | 47 | @JsonIgnore 48 | private Date updateTime; 49 | 50 | @TableLogic 51 | @JsonIgnore 52 | private Date deleteTime; 53 | } 54 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/validator/impl/DateTimeValidator.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.validator.impl; 2 | 3 | import com.lin.cms.validator.DateTimeFormat; 4 | 5 | import javax.validation.ConstraintValidator; 6 | import javax.validation.ConstraintValidatorContext; 7 | import java.text.SimpleDateFormat; 8 | 9 | public class DateTimeValidator implements ConstraintValidator { 10 | 11 | private DateTimeFormat dateTimeFormat; 12 | 13 | @Override 14 | public void initialize(DateTimeFormat dateTime) { 15 | this.dateTimeFormat = dateTime; 16 | } 17 | 18 | 19 | @Override 20 | public boolean isValid(String value, ConstraintValidatorContext context) { 21 | // 如果value为null 22 | if (value == null) { 23 | return dateTimeFormat.allowNull(); 24 | } else { 25 | String format = dateTimeFormat.format(); 26 | if (value.length() != format.length()) { 27 | return false; 28 | } 29 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format); 30 | try { 31 | simpleDateFormat.parse(value); 32 | } catch (Exception e) { 33 | return false; 34 | } 35 | return true; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/model/UserDO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableLogic; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.fasterxml.jackson.annotation.JsonIgnore; 8 | import lombok.*; 9 | 10 | import java.io.Serializable; 11 | import java.util.Date; 12 | 13 | /** 14 | * @author pedro 15 | * @since 2019-12-02 16 | */ 17 | @Data 18 | @Builder 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | @TableName("lin_user") 22 | public class UserDO implements Serializable { 23 | 24 | private static final long serialVersionUID = 1L; 25 | 26 | @TableId(value = "id", type = IdType.AUTO) 27 | private Long id; 28 | 29 | /** 30 | * 用户名,唯一 31 | */ 32 | private String username; 33 | 34 | /** 35 | * 用户昵称 36 | */ 37 | private String nickname; 38 | 39 | /** 40 | * 头像url 41 | */ 42 | private String avatar; 43 | 44 | /** 45 | * 邮箱 46 | */ 47 | private String email; 48 | 49 | @JsonIgnore 50 | private Date createTime; 51 | 52 | @JsonIgnore 53 | private Date updateTime; 54 | 55 | @JsonIgnore 56 | @TableLogic 57 | private Date deleteTime; 58 | } 59 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/service/AdminService.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.lin.cms.demo.model.PermissionDO; 5 | import com.lin.cms.demo.model.UserDO; 6 | import com.lin.cms.demo.bo.GroupPermissionsBO; 7 | import com.lin.cms.demo.dto.admin.*; 8 | import com.lin.cms.demo.model.GroupDO; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public interface AdminService { 14 | 15 | IPage getUserPageByGroupId(Long groupId, Long count, Long page); 16 | 17 | boolean changeUserPassword(Long id, ResetPasswordDTO dto); 18 | 19 | boolean deleteUser(Long id); 20 | 21 | boolean updateUserInfo(Long id, UpdateUserInfoDTO dto); 22 | 23 | IPage getGroupPage(Long page, Long count); 24 | 25 | GroupPermissionsBO getGroup(Long id); 26 | 27 | boolean createGroup(NewGroupDTO dto); 28 | 29 | boolean updateGroup(Long id, UpdateGroupDTO dto); 30 | 31 | boolean deleteGroup(Long id); 32 | 33 | boolean dispatchPermission(DispatchPermissionDTO dto); 34 | 35 | boolean dispatchPermissions(DispatchPermissionsDTO dto); 36 | 37 | boolean removePermissions(RemovePermissionsDTO dto); 38 | 39 | List getAllGroups(); 40 | 41 | List getAllPermissions(); 42 | 43 | Map> getAllStructualPermissions(); 44 | } 45 | -------------------------------------------------------------------------------- /demo/src/main/resources/mapper/FileMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /poem/src/main/java/com/lin/cms/plugins/poem/app/PageResult.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.plugins.poem.app; 2 | 3 | import java.util.List; 4 | 5 | public class PageResult { 6 | 7 | private long total; 8 | 9 | private List items; 10 | 11 | private long page; 12 | 13 | private long count; 14 | 15 | public PageResult(long total, List items, long page, long count) { 16 | this.total = total; 17 | this.items = items; 18 | this.page = page; 19 | this.count = count; 20 | } 21 | 22 | public PageResult() { 23 | } 24 | 25 | public static PageResult genPageResult(long total, List items, long page, long count) { 26 | return new PageResult(total, items, page, count); 27 | } 28 | 29 | public long getPage() { 30 | return page; 31 | } 32 | 33 | public void setPage(long page) { 34 | this.page = page; 35 | } 36 | 37 | public long getCount() { 38 | return count; 39 | } 40 | 41 | public void setCount(long count) { 42 | this.count = count; 43 | } 44 | 45 | public long getTotal() { 46 | return total; 47 | } 48 | 49 | public void setTotal(long total) { 50 | this.total = total; 51 | } 52 | 53 | public List getItems() { 54 | return items; 55 | } 56 | 57 | public void setItems(List items) { 58 | this.items = items; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /docs/starter.md: -------------------------------------------------------------------------------- 1 | # 概念 2 | 3 | ## 实现原则 4 | 5 | ### 核心库一定不能与数据库业务耦合 6 | 7 | TODO: 尝试将 异常处理 以抽象的方式交给 demo 8 | 9 | 原因主要有如下几条: 10 | 11 | 1. 不好扩展,不可定制 12 | 13 | 2. 不好修改,不好修复 14 | 15 | 3. 不好测试 16 | 17 | ### 插件一定得稳定和灵活 18 | 19 | ## 其它 20 | 21 | - PO:是 Persistant Object(持久化对象)的缩写,用于表示数据库中的一条记录映射成 22 | 的 java 对象,PO 仅仅用于表示数据,没有任何数据操作。通常遵循 Java Bean 的规范 23 | ,拥有 getter/setter 方法。 24 | 25 | - DAO:是 Data Access Object 的缩写,用于表示一个数据访问对象。使用 DAO 访问数据 26 | 库,包括增改删查等操作,与 PO 一起使用。DAO 一般在持久层,完全 1 封装数据库操 27 | 作,对外暴露的方法使得上层应用不需要关注数据库相关的任何信息。 28 | 29 | - VO:(本质上是 Controller 和 View 层交互)是 View Object 的缩写,用于一个与前 30 | 端进行交互的对象。那可以使用 PO 传递数据吗?实际上,这里的 VO 只包含前端需要展 31 | 示的数据即可,对于前端不需要的数据,比如数据库创建和修改的时间等字段,出于减少 32 | 传输数据量大小和保护数据库结构不外泄的目的,不应该在 VO 中体现出来,通常遵守 33 | Java Bean 规范,拥有 getter/setter 方法。 34 | 35 | - DTO:(本质是经过处理的 PO 对象,可能增加或减少 PO 的属性)是 Data Transfer 36 | Object 的缩写,用于表示一个数据传输对象。DTO 通常用于不同服务或服务不同分层之 37 | 间的数据传输。,DTO 和 VO 概念相似,并且通常情况下字段也基本一致。但是 DTO 和 38 | VO 又有一些不同,这个不同是设计理念上的。(敲黑板,划重点)DTO 代表服务层需要 39 | 接收的数据和返回的数据,而 VO 代表展示层需要显示的数据。DTO 通常遵循 Java Bean 40 | 的规范,拥有 getter/setter 方法。 41 | 42 | - BO:是 Business Object(业务对象)的缩写,将业务逻辑封装为一个对象,这个对象包 43 | 括一个或者多个其他的对象。比如一个简历,有教育经历、工作经历、社会关系等等。 44 | 我们可以把教育经历对应一个 PO,工作经历对应一个 PO,社会关系对应一个 PO。建立 45 | 一个对应简历的 BO 对象处理简历,每个 BO 包含这些 PO。 这样处理业务逻辑时,我们 46 | 就可以针对 BO 去处理。 47 | 48 | - POJO:是 Plain Ordinary Java Object(普通 java 对象)的缩写,表示一个简单的 49 | java 对象,上面说的 PO、VO、DTO、BO 都是典型的 POJO,而 DAO 一般不是 POJO,只 50 | 提供一些调用方法。 -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/model/LogDO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableLogic; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.fasterxml.jackson.annotation.JsonIgnore; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Builder; 10 | import lombok.Data; 11 | import lombok.NoArgsConstructor; 12 | 13 | import java.io.Serializable; 14 | import java.util.Date; 15 | 16 | /** 17 | * @author pedro 18 | * @since 2019-11-30 19 | */ 20 | @Data 21 | @TableName("lin_log") 22 | @Builder 23 | @NoArgsConstructor 24 | @AllArgsConstructor 25 | public class LogDO implements Serializable { 26 | 27 | private static final long serialVersionUID = 1L; 28 | 29 | @TableId(value = "id", type = IdType.AUTO) 30 | private Long id; 31 | 32 | private String message; 33 | 34 | private Long userId; 35 | 36 | private String username; 37 | 38 | private Integer statusCode; 39 | 40 | private String method; 41 | 42 | private String path; 43 | 44 | private String permission; 45 | 46 | @JsonIgnore 47 | private Date createTime; 48 | 49 | @JsonIgnore 50 | private Date updateTime; 51 | 52 | @TableLogic 53 | @JsonIgnore 54 | private Date deleteTime; 55 | 56 | } 57 | -------------------------------------------------------------------------------- /demo/src/main/resources/mapper/BookMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 25 | 26 | 33 | -------------------------------------------------------------------------------- /poem/src/main/java/com/lin/cms/plugins/poem/PoemMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 27 | -------------------------------------------------------------------------------- /poem/src/test/java/auth/WebConfig.java: -------------------------------------------------------------------------------- 1 | package auth; 2 | 3 | import com.lin.cms.interceptor.AuthorizeInterceptor; 4 | import com.lin.cms.interceptor.LogInterceptor; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.web.servlet.HandlerExceptionResolver; 8 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 9 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 10 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 11 | 12 | import java.util.List; 13 | 14 | @Configuration 15 | public class WebConfig implements WebMvcConfigurer { 16 | 17 | @Autowired 18 | private AuthorizeInterceptor authorizeInterceptor; 19 | 20 | @Autowired 21 | private LogInterceptor logInterceptor; 22 | 23 | //统一异常处理 24 | @Override 25 | public void configureHandlerExceptionResolvers(List exceptionResolvers) { 26 | } 27 | 28 | //解决跨域问题 29 | @Override 30 | public void addCorsMappings(CorsRegistry registry) { 31 | registry.addMapping("/**"); 32 | } 33 | 34 | // 添加拦截器 35 | // 权限拦截器 36 | @Override 37 | public void addInterceptors(InterceptorRegistry registry) { 38 | //接口签名认证拦截器,dev(生产环境)下不启用,方便测试 39 | registry.addInterceptor(authorizeInterceptor); 40 | registry.addInterceptor(logInterceptor); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /poem/src/main/java/com/lin/cms/plugins/poem/app/PoemController.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.plugins.poem.app; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RequestParam; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import java.util.List; 13 | 14 | @RestController 15 | @RequestMapping("/plugin/poem/") 16 | public class PoemController { 17 | 18 | @Value("${lin.cms.poem.limit}") 19 | private Integer limit; 20 | 21 | @Autowired 22 | private PoemMapper poemMapper; 23 | 24 | @GetMapping 25 | public PageResult index() { 26 | long pageNum = 1L; 27 | Page pager = new Page<>(pageNum, limit); 28 | IPage page = poemMapper.selectPage(pager, null); 29 | return PageResult.genPageResult(page.getTotal(), page.getRecords(), pageNum, limit); 30 | } 31 | 32 | @GetMapping("/search") 33 | public PageResult count(@RequestParam("author") String author) { 34 | // 纳兰性德 35 | List poems = poemMapper.findPoemsByAuthor(author); 36 | return PageResult.genPageResult(2, poems, 0, 2); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/model/UserIdentityDO.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableLogic; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.fasterxml.jackson.annotation.JsonIgnore; 8 | import lombok.*; 9 | 10 | import java.io.Serializable; 11 | import java.util.Date; 12 | 13 | /** 14 | * @author pedro 15 | * @since 2019-12-02 16 | */ 17 | @Setter 18 | @Getter 19 | @ToString 20 | @Builder 21 | @NoArgsConstructor 22 | @AllArgsConstructor 23 | @TableName("lin_user_identity") 24 | public class UserIdentityDO implements Serializable { 25 | 26 | private static final long serialVersionUID = 1L; 27 | 28 | @TableId(value = "id", type = IdType.AUTO) 29 | private Long id; 30 | 31 | /** 32 | * 用户id 33 | */ 34 | private Long userId; 35 | 36 | /** 37 | * 认证类型,例如 username_password,用户名-密码认证 38 | */ 39 | private String identityType; 40 | 41 | /** 42 | * 认证,例如 用户名 43 | */ 44 | private String identifier; 45 | 46 | /** 47 | * 凭证,例如 密码 48 | */ 49 | private String credential; 50 | 51 | @JsonIgnore 52 | private Date createTime; 53 | 54 | @JsonIgnore 55 | private Date updateTime; 56 | 57 | @TableLogic 58 | @JsonIgnore 59 | private Date deleteTime; 60 | } 61 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/validator/impl/EqualFieldValidator.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.validator.impl; 2 | 3 | import com.lin.cms.validator.EqualField; 4 | import org.springframework.util.ReflectionUtils; 5 | 6 | import javax.validation.ConstraintValidator; 7 | import javax.validation.ConstraintValidatorContext; 8 | import java.lang.reflect.Field; 9 | 10 | public class EqualFieldValidator implements ConstraintValidator { 11 | 12 | private String srcField; 13 | private String dstField; 14 | 15 | @Override 16 | public void initialize(EqualField constraintAnnotation) { 17 | this.srcField = constraintAnnotation.srcField(); 18 | this.dstField = constraintAnnotation.dstField(); 19 | } 20 | 21 | @Override 22 | public boolean isValid(Object object, ConstraintValidatorContext context) { 23 | Class clazz = object.getClass(); 24 | 25 | Field srcField = ReflectionUtils.findField(clazz, this.srcField); 26 | Field dstField = ReflectionUtils.findField(clazz, this.dstField); 27 | try { 28 | srcField.setAccessible(true); 29 | dstField.setAccessible(true); 30 | String src = (String) srcField.get(object); 31 | String dst = (String) dstField.get(object); 32 | if (src.equals(dst)) 33 | return true; 34 | } catch (Exception e) { 35 | return false; 36 | } 37 | return false; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.profiles.active=dev 2 | spring.mandatory-file-encoding=UTF-8 3 | # \u6240\u6709\u73AF\u5883\u901A\u7528\u7684\u914D\u7F6E\uFF0C\u653E\u5728\u8FD9\u91CC 4 | # 5 | # 404 \u4EA4\u7ED9\u5F02\u5E38\u5904\u7406\u5668\u5904\u7406 6 | spring.mvc.throw-exception-if-no-handler-found=true 7 | # \u5173\u95ED\u9759\u6001\u8D44\u6E90\u7684\u6620\u5C04 8 | spring.resources.add-mappings=false 9 | # 10 | # mybatis 11 | mybatis-plus.mapper-locations=classpath:mapper/*.xml,classpath:com/lin/cms/plugins/**/*Mapper.xml 12 | mybatis-plus.configuration.map-underscore-to-camel-case=true 13 | # \u903B\u8F91\u5220\u9664 14 | mybatis-plus.global-config.db-config.logic-delete-value=NOW() 15 | mybatis-plus.global-config.db-config.logic-not-delete-value=NULL 16 | mybatis-plus.global-config.banner=false 17 | # 18 | # \u4EE4\u724C secret 19 | lin.cms.token-secret=x88Wf0991079889x8796a0Ac68f9ecJJU17c5Vbe8beod7d8d3e695*4 20 | #lin.cms.exclude-methods= 21 | # access token \u8FC7\u671F\u65F6\u95F4\uFF0C3600s \u4E00\u4E2A\u5C0F\u65F6 22 | lin.cms.token-access-expire=3600 23 | # access token \u8FC7\u671F\u65F6\u95F4\uFF0C2592000s \u4E00\u4E2A\u6708 24 | lin.cms.token-refresh-expire=2592000 25 | # \u5F00\u542Flogger 26 | lin.cms.logger-enabled=true 27 | # 28 | # \u9ED8\u8BA4\u7684\u6743\u9650\u5206\u7EC4id\u548C\u540D\u79F0\u914D\u7F6E 29 | group.root.name=root 30 | group.root.id=1 31 | group.guest.name=guest 32 | group.guest.id=2 33 | user.root.id=1 34 | user.root.username=root 35 | user.root.nickname=root -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/validator/impl/EnumValidator.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.validator.impl; 2 | 3 | import com.lin.cms.validator.Enum; 4 | 5 | import javax.validation.ConstraintValidator; 6 | import javax.validation.ConstraintValidatorContext; 7 | import java.lang.reflect.Method; 8 | 9 | public class EnumValidator implements ConstraintValidator { 10 | 11 | private Class cls; //枚举类 12 | 13 | private boolean allowNull; 14 | 15 | 16 | @Override 17 | public void initialize(Enum constraintAnnotation) { 18 | cls = constraintAnnotation.target(); 19 | allowNull = constraintAnnotation.allowNull(); 20 | } 21 | 22 | @Override 23 | public boolean isValid(Object value, ConstraintValidatorContext context) { 24 | if (value == null && allowNull) { 25 | return true; 26 | } 27 | if (cls.isEnum()) { 28 | Object[] objs = cls.getEnumConstants(); 29 | try { 30 | Method method = cls.getMethod("getValue"); 31 | for (Object obj : objs) { 32 | Object val = method.invoke(obj); 33 | if (val.equals(value)) { 34 | return true; 35 | } 36 | } 37 | return false; 38 | } catch (Exception e) { 39 | return false; 40 | } 41 | } else { 42 | return false; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/service/PermissionService.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.service; 2 | 3 | import com.lin.cms.demo.model.PermissionDO; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * @author pedro 11 | * @since 2019-11-30 12 | */ 13 | public interface PermissionService extends IService { 14 | 15 | /** 16 | * 通过分组id得到分组的权限 17 | * 18 | * @param groupId 分组id 19 | * @return 权限 20 | */ 21 | List getPermissionByGroupId(Long groupId); 22 | 23 | /** 24 | * 通过分组id得到分组的权限 25 | * 26 | * @param groupIds 分组id 27 | * @return 权限 28 | */ 29 | List getPermissionByGroupIds(List groupIds); 30 | 31 | /** 32 | * 通过分组id得到分组的权限与分组id的映射 33 | * 34 | * @param groupIds 分组id 35 | * @return 权限map 36 | */ 37 | Map> getPermissionMapByGroupIds(List groupIds); 38 | 39 | /** 40 | * 将权限结构化 41 | * 42 | * @param permissions 权限 43 | * @return 结构化的权限 44 | */ 45 | List>>> structuringPermissions(List permissions); 46 | 47 | /** 48 | * 将权限简单地结构化 49 | * 50 | * @param permissions 权限 51 | * @return 结构化的权限 52 | */ 53 | Map> structuringPermissionsSimply(List permissions); 54 | } 55 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.2.2.RELEASE 9 | 10 | 11 | 12 | com.lin.cms.starter 13 | lin-cms-spring-boot-starter 14 | 0.0.1-SNAPSHOT 15 | lin-cms-spring-boot-starter 16 | starter project for lin cms 17 | jar 18 | 19 | 20 | UTF-8 21 | UTF-8 22 | 1.8 23 | 1.8 24 | 1.8 25 | 26 | 27 | 28 | 29 | com.lin.cms.autoconfigure 30 | lin-cms-spring-boot-autoconfigure 31 | 0.0.1-SNAPSHOT 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/exception/ParameterException.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import com.lin.cms.beans.Code; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class ParameterException extends HttpException { 11 | 12 | @Getter 13 | protected int httpCode = HttpStatus.BAD_REQUEST.value(); 14 | 15 | @Getter 16 | protected int code = Code.PARAMETER_ERROR.getCode(); 17 | 18 | private Map errors = new HashMap<>(); 19 | 20 | public ParameterException() { 21 | super(Code.PARAMETER_ERROR.getDescription()); 22 | } 23 | 24 | public ParameterException(String message) { 25 | super(message); 26 | } 27 | 28 | public ParameterException(String message, int code) { 29 | super(message); 30 | this.code = code; 31 | } 32 | 33 | public ParameterException(int code) { 34 | super(Code.PARAMETER_ERROR.getDescription()); 35 | this.code = code; 36 | } 37 | 38 | public ParameterException(Map errors) { 39 | this.errors = errors; 40 | } 41 | 42 | public ParameterException addError(String key, Object val) { 43 | this.errors.put(key, val); 44 | return this; 45 | } 46 | 47 | @Override 48 | public String getMessage() { 49 | if (errors.isEmpty()) 50 | return super.getMessage(); 51 | return errors.toString(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /demo/src/test/java/com/lin/cms/demo/mapper/FileMapperTest.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.mapper; 2 | 3 | import com.lin.cms.demo.model.FileDO; 4 | import org.junit.After; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.annotation.Rollback; 11 | import org.springframework.test.context.ActiveProfiles; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | import org.springframework.transaction.annotation.Transactional; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | 17 | @RunWith(SpringRunner.class) 18 | @SpringBootTest 19 | @Transactional 20 | @Rollback 21 | @ActiveProfiles("test") 22 | public class FileMapperTest { 23 | 24 | @Autowired 25 | private FileMapper fileMapper; 26 | 27 | private String md5 = "iiiiilllllll"; 28 | private String name = "千里之外"; 29 | 30 | @Before 31 | public void setUp() throws Exception { 32 | FileDO fileDO = new FileDO(); 33 | fileDO.setName(name); 34 | fileDO.setPath("千里之外..."); 35 | fileDO.setSize(1111); 36 | fileDO.setExtension(".png"); 37 | fileDO.setMd5(md5); 38 | fileMapper.insert(fileDO); 39 | } 40 | 41 | @Test 42 | public void testFindOneByMd5() { 43 | FileDO one = fileMapper.selectByMd5(md5); 44 | assertEquals(one.getName(), name); 45 | } 46 | 47 | @After 48 | public void tearDown() throws Exception { 49 | } 50 | } -------------------------------------------------------------------------------- /poem/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | # \u4EE4\u724C secret 2 | lin.cms.token-secret=x88Wf0991079889x8796a0Ac68f9ecJJU17c5Vbe8beod7d8d3e695*4 3 | # access token \u8FC7\u671F\u65F6\u95F4\uFF0C3600s \u4E00\u4E2A\u5C0F\u65F6 4 | lin.cms.token-access-expire=3600 5 | # access token \u8FC7\u671F\u65F6\u95F4\uFF0C2592000s \u4E00\u4E2A\u6708 6 | lin.cms.token-refresh-expire=2592000 7 | # \u5F00\u542Flogger 8 | lin.cms.logger-enabled=true 9 | # \u6240\u6709\u73AF\u5883\u901A\u7528\u7684\u914D\u7F6E\uFF0C\u653E\u5728\u8FD9\u91CC 10 | # 11 | # 404 \u4EA4\u7ED9\u5F02\u5E38\u5904\u7406\u5668\u5904\u7406 12 | spring.mvc.throw-exception-if-no-handler-found=true 13 | # \u5173\u95ED\u9759\u6001\u8D44\u6E90\u7684\u6620\u5C04 14 | spring.resources.add-mappings=false 15 | # 16 | # mybatis 17 | mybatis.mapper-locations=classpath:mapper/*.xml,classpath:com/lin/cms/plugins/**/*Mapper.xml 18 | mybatis.type-aliases-package=com.lin.cms.demo.model 19 | mybatis.configuration.map-underscore-to-camel-case=true 20 | # 21 | server.port=5000 22 | # \u5F00\u53D1\u73AF\u5883\u914D\u7F6E 23 | # \u6570\u636E\u6E90\u914D\u7F6E\uFF0C\u8BF7\u4FEE\u6539\u4E3A\u4F60\u9879\u76EE\u7684\u5B9E\u9645\u914D\u7F6E 24 | spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false 25 | spring.datasource.username=root 26 | spring.datasource.password=123456 27 | spring.datasource.driver-class-name=com.mysql.jdbc.Driver 28 | # 29 | # 30 | # \u9759\u6001\u8D44\u6E90\u7684\u670D\u52A1 31 | # 32 | # \u65E5\u5FD7\u8BB0\u5F55\u95EE\u9898 33 | # 34 | logging.level.web=DEBUG 35 | logging.level.com.lin.cms=ERROR 36 | spring.servlet.multipart.max-file-size=20MB -------------------------------------------------------------------------------- /demo/authority.md: -------------------------------------------------------------------------------- 1 | # 权限v2总览 2 | 3 | ## 模式 4 | 5 | 6 | ```sh 7 | 8 | -------------------------------------------- 9 | | | 10 | | |--------| | 11 | | user --> | role | --> | 12 | | \ |--------| | 13 | | \ permission | 14 | | \ -------------------> | 15 | | | 16 | -------------------------------------------- 17 | ``` 18 | 19 | 20 | ```mermaid 21 | graph LR 22 | user --> role 23 | role --> permission 24 | user --> permission 25 | ``` 26 | 27 | - 严格的上下级权限,只有上级可以管理下级,可以越级 28 | - 超级管理员(super/root)同样是一个角色,一个凌驾于所有的角色 29 | - 当一个用户被创建时,默认拥有`guest(游客)`角色,该角色拥有基本的访问和登陆 30 | - role(角色)是权限的集合,是一个抽象的概念 31 | - `role`有层级(level)之分,上级角色若拥有管理权限,可以管理下级的角色,否则只是结构的上、下级 32 | - 操作权包括:下级角色创建权(create)、下级角色更新权(update)、下级角色删除权(delete)、权限分配权(grant),权限回收权(recycle)。 33 | - 用户在分配,回收权限的时候只能以自己的权限为基础,如果自己都没有的权限不能分配给下级和同级,不能回收自己都没有的权限 34 | - 上级拥有下级的绝对管理权,下级不能管理上级;即使下级角色拥有了角色删除权,但是受层级限制,他也只能删除他的下级角色 35 | - 层级结构跟unix文件树结构一样,已 /root 为跟节点依次向下延生。 36 | 37 | ## 数据库设计 38 | 39 | 见`schema.sql` 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 | 如果用户已经拥有了权限,分配用户角色时,如果该角色已经有了该权限,合并?OR 并有? 67 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/interceptor/LogInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.interceptor; 2 | 3 | import com.lin.cms.core.annotation.Logger; 4 | import com.lin.cms.core.annotation.RouteMeta; 5 | import com.lin.cms.interfaces.LoggerResolver; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.method.HandlerMethod; 8 | import org.springframework.web.servlet.ModelAndView; 9 | import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.lang.reflect.Method; 14 | 15 | public class LogInterceptor extends HandlerInterceptorAdapter { 16 | 17 | @Autowired 18 | private LoggerResolver loggerResolver; 19 | 20 | @Override 21 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 22 | // 记录日志 23 | if (handler instanceof HandlerMethod) { 24 | HandlerMethod handlerMethod = (HandlerMethod) handler; 25 | Method method = handlerMethod.getMethod(); 26 | Logger logger = method.getAnnotation(Logger.class); 27 | RouteMeta meta = method.getAnnotation(RouteMeta.class); 28 | if (logger != null) { 29 | // parse template and extract properties from request,response and modelAndView 30 | loggerResolver.handle(meta, logger, request, response); 31 | } 32 | } 33 | super.postHandle(request, response, handler, modelAndView); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /demo/src/main/resources/mapper/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 24 | 25 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /docs/extension.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 扩展 3 | --- 4 | 5 | # 扩展 6 | 7 | ## 概述 8 | 9 | 小皮同学接到了一个开发任务:开发一个基于zookeeper的配置中心。熟悉zookeeper 10 | 的小皮信心满满,两三天就将这个功能开发完毕,单测、文档均已搞定,于是汇报给了 11 | leader。 12 | 13 | leader 很是满意,不过前方运维传来了不幸的消息,zookeeper集群出现重大问题, 14 | 不能再分配相应机器供配置调用。没有办法,只能安排小皮使用公司的etcd集群重新 15 | 开发配置中心了。 16 | 17 | 小皮很是难过,只能硬着头皮进行魔改。重构的时候,小皮发现原来的代码严重耦合, 18 | 虽然业务完成了,但不易维护和添加新特性。小皮苦思良久,又花了一个星期的时间 19 | 将配置中心完全重构,现在配置中心不仅支持etcd和zookeeper,而且还能一键切换 20 | ,leader很是高兴,奖励了小皮。 21 | 22 | 对于一个业务接口,不同的具体实现,支持一键切换,这样的开发模式是解决复杂业务, 23 | 保持系统稳定的一个重要解决方案。 24 | 25 | lin-cms将这样的功能点单独抽象出了一个概念——`扩展`。扩展应具有以下几种特点: 26 | 27 | 1. 一种功能,多种实现。 28 | 2. 独立于项目,不依赖项目的业务。 29 | 3. 实现替换简单,开箱即用 30 | 31 | 32 | ## 实例 33 | 34 | 我们还是以`file`作为例子来详细的讲解扩展的使用,文件扩展的代码目录结构如下: 35 | 36 | ```bash 37 | ├── File.java 38 | ├── FileConsts.java 39 | ├── FileProperties.java 40 | ├── FileUtil.java 41 | ├── LocalUploader.java 42 | ├── PreHandler.java 43 | ├── Uploader.java 44 | └── config.properties 45 | ``` 46 | 47 | 首先,你可以将扩展理解为一个业务点,既然是业务点,那么一定会对外提供业务支持; 48 | 没错,`file`对外提供`Uploader`的业务支持,即文件上传的业务支持。 49 | 50 | `Uploader`是一个接口,同时它也是一个出口,一个提供文件上传服务的出口。而其它 51 | 文件都是服务于`Uploader`的,如`File`是上传文件信息的数据容器,`FileUtil`则是 52 | 基础的工具类。 53 | 54 | 既然`Uploader`是一个服务接口,那么它必然有服务使用者,目前这个服务的使用者是 55 | 业务层中的`FileService`。 56 | 57 | 好了,我们回到正题,继续来谈代码结构;FileConsts是字符串常量相关类,方便字符串的 58 | 维护;FileProperties是文件上传的相关配置类,它与`config.properties`搭配,一个提供 59 | 配置文件,一个提供程序配置类;LocalUploader是`Uploader`的具体实现,它是第一个默认实现, 60 | 你也可以提供相关的实现来满足你自己的业务。PreHandler非必须,只是`Uploader`业务实现的 61 | 一个点。 62 | 63 | 谈到这里,我们来总结一下扩展: 64 | 65 | 1. 扩展其实就是一个微型接口,它有自己独立的配置,独立的逻辑和实现。 66 | 2. 扩展必须独立,它不应该依赖工程,而应该让工程依赖于它。 67 | 3. 扩展必须提供一个接口作为出口,该接口可以有多个实现。 68 | 69 | ## 实践 70 | 71 | 说了那么多,你或许还是云里雾里,下面我们将开发一个基础`限流扩展`来实操一下。 72 | 73 | TODO 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/extensions/file/FileProperties.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.extensions.file; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.context.annotation.PropertySource; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | @ConfigurationProperties(prefix = "lin.cms.file") 9 | @PropertySource(value = "classpath:com/lin/cms/demo/extensions/file/config.properties", encoding = "UTF-8") 10 | public class FileProperties { 11 | 12 | 13 | private static final String[] DEFAULT_EMPTY_ARRAY = new String[0]; 14 | 15 | private String dir = "/assets"; 16 | 17 | private String singleLimit = "2MB"; 18 | 19 | private Integer nums = 10; 20 | 21 | private String[] exclude = DEFAULT_EMPTY_ARRAY; 22 | 23 | private String[] include = DEFAULT_EMPTY_ARRAY; 24 | 25 | public String getDir() { 26 | return dir; 27 | } 28 | 29 | public void setDir(String dir) { 30 | this.dir = dir; 31 | } 32 | 33 | public String getSingleLimit() { 34 | return singleLimit; 35 | } 36 | 37 | public void setSingleLimit(String singleLimit) { 38 | this.singleLimit = singleLimit; 39 | } 40 | 41 | public Integer getNums() { 42 | return nums; 43 | } 44 | 45 | public void setNums(Integer nums) { 46 | this.nums = nums; 47 | } 48 | 49 | public String[] getExclude() { 50 | return exclude; 51 | } 52 | 53 | public void setExclude(String[] exclude) { 54 | this.exclude = exclude; 55 | } 56 | 57 | public String[] getInclude() { 58 | return include; 59 | } 60 | 61 | public void setInclude(String[] include) { 62 | this.include = include; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.annotation.PropertySources; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | /** 11 | * 资源文件路径,可以是数据多个文件地址 12 | * 可以是classpath地址如: 13 | * "classpath:/com/app.properties" 14 | * 也可以是对应的文件系统地址如: 15 | * "file:/path/to/file" 16 | */ 17 | 18 | @SpringBootApplication(scanBasePackages = {"com.lin.cms.demo"}) 19 | @MapperScan(basePackages = {"com.lin.cms.demo.mapper"/*, "com.lin.cms.plugins.poem.app"*/}) 20 | @PropertySources({ 21 | // @PropertySource("classpath:com/lin/cms/plugins/poem/plugin.properties"), 22 | }) 23 | @RestController 24 | public class DemoApplication { 25 | 26 | public static void main(String[] args) { 27 | SpringApplication.run(DemoApplication.class, args); 28 | } 29 | 30 | @RequestMapping("/") 31 | public String index() { 32 | return "

" + 36 | "Lin
心上无垢,林间有风。

"; 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/autoconfigure/LinCmsProperties.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.autoconfigure; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | 6 | @ConfigurationProperties("lin.cms") 7 | public class LinCmsProperties { 8 | 9 | private static final String[] DEFAULT_EXCLUDE_METHODS = new String[]{"OPTIONS"}; 10 | 11 | private String tokenSecret = ""; 12 | 13 | private String[] excludeMethods = DEFAULT_EXCLUDE_METHODS; 14 | 15 | private Long tokenAccessExpire = 3600L; 16 | 17 | private Long tokenRefreshExpire = 2592000L; 18 | 19 | private boolean loggerEnabled = true; 20 | 21 | public boolean isLoggerEnabled() { 22 | return loggerEnabled; 23 | } 24 | 25 | public void setLoggerEnabled(boolean loggerEnabled) { 26 | this.loggerEnabled = loggerEnabled; 27 | } 28 | 29 | public String getTokenSecret() { 30 | return tokenSecret; 31 | } 32 | 33 | public void setTokenSecret(String tokenSecret) { 34 | this.tokenSecret = tokenSecret; 35 | } 36 | 37 | public Long getTokenAccessExpire() { 38 | return tokenAccessExpire; 39 | } 40 | 41 | public void setTokenAccessExpire(Long tokenAccessExpire) { 42 | this.tokenAccessExpire = tokenAccessExpire; 43 | } 44 | 45 | public Long getTokenRefreshExpire() { 46 | return tokenRefreshExpire; 47 | } 48 | 49 | public void setTokenRefreshExpire(Long tokenRefreshExpire) { 50 | this.tokenRefreshExpire = tokenRefreshExpire; 51 | } 52 | 53 | public String[] getExcludeMethods() { 54 | return excludeMethods; 55 | } 56 | 57 | public void setExcludeMethods(String[] excludeMethods) { 58 | this.excludeMethods = excludeMethods; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # demo 2 | 3 | 4 | **打包** 5 | 6 | ```bash 7 | mvn clean package -Dmaven.test.skip=true 8 | ``` 9 | 10 | **启动** 11 | 12 | ```bash 13 | java -jar target/demo-0.0.1-SNAPSHOT.jar --server.port=8888 --spring.profiles.active=prod 14 | ``` 15 | 16 | 17 | ``` 18 | SpringBoot 开发电商CMS服务端(Lin-CMS-Java) 19 | SpringBoot便捷开发模式(自动装备与自动配置) 20 | Lin CMS架构模式 21 | 标准业务分层REST开发规范 22 | 前后端分离模式下的权限方案 23 | 自定义validator校验器 24 | 自定义logback配置 25 | 多环境配置文件 26 | 自定义骚气banner 27 | 统一异常处理,统一返回结果规范 28 | fastjson默认json序列化方案 29 | fastjson前端参数名转换 30 | druid数据库连接池集成 31 | JWT令牌发放与校验 32 | 前端权限都是假权限,服务端权限才是核心 33 | 自定义认证和授权拦截器 34 | 自定义Http请求记录拦截器 35 | CMS服务端权限管理与注解守卫函数 36 | CMS用户日志记录功能 37 | MyBatis与MyBatis Plus 38 | MyBatis Plus自定义分页 39 | MyBatis Plus逻辑删除(软删除)拦截器 40 | MyBatis Plus一键生成controller,service,mapper,model 41 | Lin CMS扩展开发 42 | Lin CMS文件上传系统 43 | 44 | 首页轮播图的展示与管理 45 | 多级分类的展示与管理 46 | 商品品牌的展示与管理 47 | 首页主题的展示与管理 48 | 首页六宫格的展示与管理 49 | 活动(页)的展示与管理 50 | 优惠卷折扣解决方案 51 | 活动页、分类、优惠卷的联动展示与管理 52 | 标准SPU与SKU的解决方案 53 | 规格的展示与管理 54 | SPU、SKU、规格的联动展示与管理 55 | 小程序用户数据展示 56 | 用户订单数据展示 57 | ``` 58 | 59 | 60 | ## 优化 61 | 62 | 更改分页返回结果 63 | 64 | 更改数组返回结果 65 | 66 | 数组和分页未找到数据,皆返回200,单个数据未找到返回404 67 | 68 | 69 | ## 数据统计与展示 70 | 71 | 周期性活跃用户 72 | 73 | 周期性订单数量,金额总量 74 | 75 | 今日支付金额,周期性支付金额 76 | 77 | 用户数量统计,区域划分 78 | 79 | 待补充。。。 80 | 81 | 将开源的page返回改下 82 | 83 | datetime(3) 84 | 85 | activity and others .... 86 | 87 | *图片丢在第二列,hover之后显示全称,文字hover,太长省略 88 | 89 | *tabbar fixed * 90 | 91 | 表格层次,表格10个 - 92 | 93 | 订单状态,筛选,状态切换,UNPAID和CANCELED,已发货,已完成的改变,添加超时异常 94 | 95 | 图表的颜色 96 | 97 | 无折扣,第一个框禁用 98 | 99 | spu 测试 100 | 101 | refresh_token是否失效 102 | 103 | 按照code.md同步异常类即可 104 | 105 | username nickname 106 | 107 | page,count,items,total,total_page 108 | 109 | jackson 110 | 111 | 多级权限 -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/beans/Code.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.beans; 2 | 3 | 4 | /** 5 | * 消息码 6 | */ 7 | public enum Code { 8 | 9 | SUCCESS(0, "OK", "成功"), 10 | 11 | CREATED(1, "Created", "创建成功"), 12 | 13 | FAIL(10200, "Failed", "失败"), 14 | 15 | UN_AUTHORIZATION(10000, "Authorization Failed", "认证失败"), 16 | 17 | UN_AUTHENTICATION(10010, "Authentication Failed", "授权失败"), 18 | 19 | NOT_FOUND(10020, "Not Found", "资源不存在"), 20 | 21 | PARAMETER_ERROR(10030, "Parameters Error", "参数错误"), 22 | 23 | TOKEN_INVALID(10040, "Token Invalid", "令牌失效"), 24 | 25 | TOKEN_EXPIRED(10050, "Token Expired", "令牌过期"), 26 | 27 | REPEAT(10060, "Repeat", "字段重复"), 28 | 29 | INTERNAL_SERVER_ERROR(9999, "Internal Server Error", "服务器未知错误"), 30 | 31 | FORBIDDEN(10070, "Forbidden", "禁止操作"), 32 | 33 | METHOD_NOT_ALLOWED(10080, "Method Not Allowed", "请求方法不允许"), 34 | 35 | REFRESH_FAILED(10100, "Get Refresh Token Failed", "刷新令牌获取失败"), 36 | 37 | FILE_TOO_LARGE(10110, "File Too Large", "文件体积过大"), 38 | 39 | FILE_TOO_MANY(10120, "File Too Many", "文件数量过多"), 40 | 41 | FILE_EXTENSION(10130, "File Extension Not Allowed", "文件扩展名不符合规范"), 42 | 43 | REQUEST_LIMIT(10140, "Too Many Requests", "请求过于频繁,请稍后重试"); 44 | 45 | private int code; 46 | 47 | private String description; 48 | 49 | private String zhDescription; 50 | 51 | Code(int code, String description, String zhDescription) { 52 | this.code = code; 53 | this.description = description; 54 | this.zhDescription = zhDescription; 55 | } 56 | 57 | public int getCode() { 58 | return code; 59 | } 60 | 61 | public String getDescription() { 62 | return description; 63 | } 64 | 65 | public String getZhDescription() { 66 | return zhDescription; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /demo/src/main/resources/mapper/PermissionMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 31 | 32 | 44 | 45 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/controller/cms/TestController.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.controller.cms; 2 | 3 | import com.lin.cms.core.annotation.GroupRequired; 4 | import com.lin.cms.core.annotation.Logger; 5 | import com.lin.cms.core.annotation.LoginRequired; 6 | import com.lin.cms.core.annotation.RouteMeta; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | @RestController 14 | @RequestMapping("/cms/test") 15 | public class TestController { 16 | 17 | @RequestMapping("") 18 | public String index() { 19 | return "

" + 23 | "Lin
心上无垢,林间有风。

"; 24 | } 25 | 26 | @RequestMapping("/json") 27 | @RouteMeta(permission = "测试日志记录", module = "信息", mount = true) 28 | @LoginRequired 29 | @Logger(template = "{user.nickname}又皮了一波") 30 | public Map getTestMsg() { 31 | Map res = new HashMap(); 32 | res.put("msg", "物质决定意识,经济基础决定上层建筑"); 33 | return res; 34 | } 35 | 36 | @RequestMapping("/info") 37 | @RouteMeta(permission = "查看lin的信息", module = "信息", mount = true) 38 | @GroupRequired 39 | public Map getTestInfo() { 40 | Map res = new HashMap(); 41 | res.put("msg", "Lin 是一套基于 Spring boot 的一整套开箱即用的后台管理系统(CMS)。Lin 遵循简洁、高效的原则,通过核心库加插件的方式来驱动整个系统高效的运行"); 42 | return res; 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/service/impl/BookServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.service.impl; 2 | 3 | import com.lin.cms.demo.dto.book.CreateOrUpdateBookDTO; 4 | import com.lin.cms.demo.mapper.BookMapper; 5 | import com.lin.cms.demo.model.BookDO; 6 | import com.lin.cms.demo.service.BookService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.List; 11 | 12 | @Service 13 | public class BookServiceImpl implements BookService { 14 | 15 | @Autowired 16 | private BookMapper bookMapper; 17 | 18 | @Override 19 | public boolean createBook(CreateOrUpdateBookDTO validator) { 20 | BookDO book = new BookDO(); 21 | book.setAuthor(validator.getAuthor()); 22 | book.setTitle(validator.getTitle()); 23 | book.setImage(validator.getImage()); 24 | book.setSummary(validator.getSummary()); 25 | return bookMapper.insert(book) > 0; 26 | } 27 | 28 | @Override 29 | public List getBookByKeyword(String q) { 30 | List books = bookMapper.selectByTitleLikeKeyword(q); 31 | return books; 32 | } 33 | 34 | @Override 35 | public boolean updateBook(BookDO book, CreateOrUpdateBookDTO validator) { 36 | book.setAuthor(validator.getAuthor()); 37 | book.setTitle(validator.getTitle()); 38 | book.setImage(validator.getImage()); 39 | book.setSummary(validator.getSummary()); 40 | return bookMapper.updateById(book) > 0; 41 | } 42 | 43 | @Override 44 | public BookDO getById(Long id) { 45 | BookDO book = bookMapper.selectById(id); 46 | return book; 47 | } 48 | 49 | @Override 50 | public List findAll() { 51 | List books = bookMapper.selectList(null); 52 | return books; 53 | } 54 | 55 | @Override 56 | public boolean deleteById(Long id) { 57 | return bookMapper.deleteById(id) > 0; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/autoconfigure/LinCmsAutoConfigure.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.autoconfigure; 2 | 3 | import com.lin.cms.interceptor.AuthorizeInterceptor; 4 | import com.lin.cms.interceptor.LogInterceptor; 5 | import com.lin.cms.core.token.DoubleJWT; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.core.Ordered; 12 | import org.springframework.core.annotation.Order; 13 | 14 | @Configuration(proxyBeanMethods = false) 15 | @Order(Ordered.HIGHEST_PRECEDENCE) 16 | @EnableConfigurationProperties(LinCmsProperties.class) 17 | public class LinCmsAutoConfigure { 18 | 19 | @Autowired 20 | private LinCmsProperties properties; 21 | 22 | @Bean 23 | public DoubleJWT jwter() { 24 | String secret = properties.getTokenSecret(); 25 | Long accessExpire = properties.getTokenAccessExpire(); 26 | Long refreshExpire = properties.getTokenRefreshExpire(); 27 | if (accessExpire == null) { 28 | // 一个小时 29 | accessExpire = 60 * 60L; 30 | } 31 | if (refreshExpire == null) { 32 | // 一个月 33 | refreshExpire = 60 * 60 * 24 * 30L; 34 | } 35 | return new DoubleJWT(secret, accessExpire, refreshExpire); 36 | } 37 | 38 | @Bean 39 | public AuthorizeInterceptor authInterceptor() { 40 | String[] excludeMethods = properties.getExcludeMethods(); 41 | return new AuthorizeInterceptor(excludeMethods); 42 | } 43 | 44 | @Bean 45 | @ConditionalOnProperty(prefix = "lin.cms", value = "logger-enabled", havingValue = "true") 46 | public LogInterceptor logInterceptor() { 47 | return new LogInterceptor(); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /demo/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////// 2 | ______ _ 3 | | ___ \ | | 4 | | |_/ /__ __| |_ __ ___ 5 | | __/ _ \/ _` | '__/ _ \ 6 | | | | __/ (_| | | | (_) | 7 | \_| \___|\__,_|_| \___/ 8 | 9 | //////////////////////////////////////////////////////////////////// 10 | // _ooOoo_ // 11 | // o8888888o // 12 | // 88" . "88 // 13 | // (| ^_^ |) // 14 | // O\ = /O // 15 | // ____/`---'\____ // 16 | // .' \\| |// `. // 17 | // / \\||| : |||// \ // 18 | // / _||||| -:- |||||- \ // 19 | // | | \\\ - /// | | // 20 | // | \_| ''\---/'' | | // 21 | // \ .-\__ `-` ___/-. / // 22 | // ___`. .' /--.--\ `. . ___ // 23 | // ."" '< `.___\_<|>_/___.' >'"". // 24 | // | | : `- \`.;`\ _ /`;.`/ - ` : | | // 25 | // \ \ `-. \_ __\ /__ _/ .-` / / // 26 | // ========`-.____`-.___\_____/___.-`____.-'======== // 27 | // `=---=' // 28 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // 29 | // 佛祖保佑 永不宕机 永无BUG // 30 | //////////////////////////////////////////////////////////////////// 31 | 解放生产力 32 | //////////////////////////////////////////////////////////////////// -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/test/java/com/lin/cms/exception/ParameterExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.junit.Test; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import static org.junit.Assert.*; 10 | 11 | @Slf4j 12 | public class ParameterExceptionTest { 13 | 14 | @Test 15 | public void test() { 16 | ParameterException exception = new ParameterException(); 17 | String message = exception.getMessage(); 18 | String localizedMessage = exception.getLocalizedMessage(); 19 | int code = exception.getCode(); 20 | assertEquals(10030, code); 21 | assertEquals("Parameters Error", message); 22 | assertEquals("Parameters Error", localizedMessage); 23 | } 24 | 25 | @Test 26 | public void test1() { 27 | ParameterException exception = new ParameterException("pedro犯了一个错误"); 28 | String message = exception.getMessage(); 29 | String localizedMessage = exception.getLocalizedMessage(); 30 | int code = exception.getCode(); 31 | assertEquals(10030, code); 32 | assertEquals("pedro犯了一个错误", message); 33 | assertEquals("pedro犯了一个错误", localizedMessage); 34 | } 35 | 36 | @Test 37 | public void test2() { 38 | Map errors = new HashMap<>(); 39 | errors.put("nickname", "名称不能超过100字符"); 40 | errors.put("age", "年龄不能为负数"); 41 | ParameterException exception = new ParameterException(errors); 42 | String message = exception.getMessage(); 43 | int code = exception.getCode(); 44 | assertEquals(10030, code); 45 | assertEquals("{nickname=名称不能超过100字符, age=年龄不能为负数}", message); 46 | } 47 | 48 | @Test 49 | public void test3() { 50 | ParameterException exception = new ParameterException(); 51 | exception.addError("nickname", "名称不能超过100字符"); 52 | String message = exception.getMessage(); 53 | int code = exception.getCode(); 54 | assertEquals(10030, code); 55 | assertEquals("{nickname=名称不能超过100字符}", message); 56 | } 57 | } -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/common/utils/ResponseUtil.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.common.utils; 2 | 3 | import com.lin.cms.demo.vo.UnifyResponseVO; 4 | import com.lin.cms.exception.HttpException; 5 | import com.lin.cms.response.Created; 6 | import com.lin.cms.response.Success; 7 | import com.lin.cms.beans.Code; 8 | import com.lin.cms.utils.RequestUtil; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | 12 | /** 13 | * 响应结果生成工具 14 | */ 15 | @Slf4j 16 | public class ResponseUtil { 17 | 18 | public static UnifyResponseVO generateUnifyResponse(HttpException e) { 19 | return UnifyResponseVO.builder() 20 | .message(e.getMessage()) 21 | .code(e.getCode()) 22 | .request(RequestUtil.getSimpleRequest()) 23 | .build(); 24 | } 25 | 26 | public static UnifyResponseVO generateSuccessResponse(T data) { 27 | Success success = new Success(); 28 | return (UnifyResponseVO) UnifyResponseVO.builder() 29 | .message(data) 30 | .code(success.getCode()) 31 | .request(RequestUtil.getSimpleRequest()) 32 | .build(); 33 | } 34 | 35 | public static UnifyResponseVO generateUnifyResponse(int errorCode) { 36 | return (UnifyResponseVO) UnifyResponseVO.builder() 37 | .code(errorCode) 38 | .request(RequestUtil.getSimpleRequest()) 39 | .build(); 40 | } 41 | 42 | public static UnifyResponseVO generateCreatedResponse(T data) { 43 | Created created = new Created(); 44 | return (UnifyResponseVO) UnifyResponseVO.builder() 45 | .message(data) 46 | .code(created.getCode()) 47 | .request(RequestUtil.getSimpleRequest()) 48 | .build(); 49 | } 50 | 51 | public static UnifyResponseVO generateUnifyResponse(Code code, int httpCode) { 52 | return (UnifyResponseVO) UnifyResponseVO.builder() 53 | .code(code.getCode()) 54 | .message(code.getDescription()) 55 | .request(RequestUtil.getSimpleRequest()) 56 | .build(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/service/GroupService.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.lin.cms.demo.bo.GroupPermissionsBO; 5 | import com.lin.cms.demo.model.GroupDO; 6 | import com.baomidou.mybatisplus.extension.service.IService; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @author pedro 12 | * @since 2019-11-30 13 | */ 14 | public interface GroupService extends IService { 15 | 16 | /** 17 | * 获得用户的所有分组 18 | * 19 | * @param userId 用户id 20 | * @return 所有分组 21 | */ 22 | List getUserGroupsByUserId(Long userId); 23 | 24 | /** 25 | * 获得用户的所有分组id 26 | * 27 | * @param userId 用户id 28 | * @return 所有分组id 29 | */ 30 | List getUserGroupIdsByUserId(Long userId); 31 | 32 | /** 33 | * 分页获取分组数据 34 | * 35 | * @param count 分页数量 36 | * @param page 那一页 37 | * @return 分组页 38 | */ 39 | IPage getGroupPage(long page, long count); 40 | 41 | /** 42 | * 通过id检查分组是否存在 43 | * 44 | * @param id 分组id 45 | * @return 是否存在 46 | */ 47 | boolean checkGroupExistById(Long id); 48 | 49 | /** 50 | * 获得分组及其权限 51 | * 52 | * @param id 分组id 53 | * @return 分组及权限 54 | */ 55 | GroupPermissionsBO getGroupAndPermissions(Long id); 56 | 57 | /** 58 | * 通过名称检查分组是否存在 59 | * 60 | * @param name 分组名 61 | * @return 是否存在 62 | */ 63 | boolean checkGroupExistByName(String name); 64 | 65 | /** 66 | * 检查该用户是否在root分组中 67 | * 68 | * @param userId 用户id 69 | * @return true表示在 70 | */ 71 | boolean checkIsRootByUserId(Long userId); 72 | 73 | /** 74 | * 删除用户与分组直接的关联 75 | * 76 | * @param userId 用户id 77 | * @param deleteIds 分组id 78 | */ 79 | boolean deleteUserGroupRelations(Long userId, List deleteIds); 80 | 81 | /** 82 | * 添加用户与分组直接的关联 83 | * 84 | * @param userId 用户id 85 | * @param addIds 分组id 86 | */ 87 | boolean addUserGroupRelations(Long userId, List addIds); 88 | } 89 | -------------------------------------------------------------------------------- /demo/src/test/java/com/lin/cms/demo/mapper/BookMapperTest.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.mapper; 2 | 3 | import com.lin.cms.demo.model.BookDO; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.annotation.Rollback; 9 | import org.springframework.test.context.ActiveProfiles; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | import java.util.List; 14 | 15 | import static org.junit.Assert.*; 16 | 17 | @RunWith(SpringRunner.class) 18 | @SpringBootTest 19 | @Transactional 20 | @Rollback 21 | @ActiveProfiles("test") 22 | public class BookMapperTest { 23 | 24 | @Autowired 25 | private BookMapper bookMapper; 26 | 27 | public BookDO initData() { 28 | String title = "千里之外"; 29 | String author = "pedro"; 30 | String image = "千里之外.png"; 31 | String summary = "千里之外,是周杰伦和费玉清一起发售的歌曲"; 32 | BookDO bookDO = new BookDO(); 33 | bookDO.setTitle(title); 34 | bookDO.setAuthor(author); 35 | bookDO.setImage(image); 36 | bookDO.setSummary(summary); 37 | bookMapper.insert(bookDO); 38 | return bookDO; 39 | } 40 | 41 | 42 | @Test 43 | public void selectByTitleLikeKeyword() { 44 | BookDO book = initData(); 45 | List found = bookMapper.selectByTitleLikeKeyword("%千里%"); 46 | boolean anyMatch = found.stream().anyMatch(it -> it.getTitle().equals(book.getTitle())); 47 | assertTrue(anyMatch); 48 | } 49 | 50 | 51 | @Test 52 | public void selectById() { 53 | BookDO book = initData(); 54 | BookDO found = bookMapper.selectById(book.getId()); 55 | assertEquals(found.getTitle(), book.getTitle()); 56 | } 57 | 58 | @Test 59 | public void selectByTitle() { 60 | BookDO book = initData(); 61 | List found = bookMapper.selectByTitle(book.getTitle()); 62 | boolean anyMatch = found.stream().anyMatch(it -> it.getTitle().equals(book.getTitle())); 63 | assertTrue(anyMatch); 64 | } 65 | } -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/extensions/file/FileUtil.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.extensions.file; 2 | 3 | import org.springframework.util.DigestUtils; 4 | import org.springframework.util.unit.DataSize; 5 | 6 | import java.io.File; 7 | import java.nio.file.FileSystem; 8 | import java.nio.file.FileSystems; 9 | import java.nio.file.Path; 10 | 11 | public class FileUtil { 12 | 13 | public static FileSystem getDefaultFileSystem() { 14 | return FileSystems.getDefault(); 15 | } 16 | 17 | public static boolean isAbsolute(String str) { 18 | Path path = getDefaultFileSystem().getPath(str); 19 | return path.isAbsolute(); 20 | } 21 | 22 | @SuppressWarnings("ResultOfMethodCallIgnored") 23 | public static void initStoreDir(String dir) { 24 | String absDir; 25 | if (isAbsolute(dir)) { 26 | absDir = dir; 27 | } else { 28 | String cmd = getCmd(); 29 | Path path = getDefaultFileSystem().getPath(cmd, dir); 30 | absDir = path.toAbsolutePath().toString(); 31 | } 32 | File file = new File(absDir); 33 | if (!file.exists()) { 34 | file.mkdirs(); 35 | } 36 | } 37 | 38 | public static String getCmd() { 39 | return System.getProperty("user.dir"); 40 | } 41 | 42 | public static String getFileAbsolutePath(String dir, String filename) { 43 | if (isAbsolute(dir)) { 44 | return getDefaultFileSystem() 45 | .getPath(dir, filename) 46 | .toAbsolutePath().toString(); 47 | } else { 48 | return getDefaultFileSystem() 49 | .getPath(getCmd(), dir, filename) 50 | .toAbsolutePath().toString(); 51 | } 52 | } 53 | 54 | public static String getFileExt(String filename) { 55 | int index = filename.lastIndexOf('.'); 56 | return filename.substring(index); 57 | } 58 | 59 | public static String getFileMD5(byte[] bytes) { 60 | return DigestUtils.md5DigestAsHex(bytes); 61 | } 62 | 63 | public static Long parseSize(String size) { 64 | DataSize singleLimitData = DataSize.parse(size); 65 | return singleLimitData.toBytes(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/service/impl/LogServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.lin.cms.demo.common.mybatis.Page; 5 | import com.lin.cms.demo.model.LogDO; 6 | import com.lin.cms.demo.mapper.LogMapper; 7 | import com.lin.cms.demo.service.LogService; 8 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.Date; 12 | 13 | /** 14 | * @author pedro 15 | * @since 2019-11-30 16 | */ 17 | @Service 18 | public class LogServiceImpl extends ServiceImpl implements LogService { 19 | 20 | @Override 21 | public IPage getLogs(Long page, Long count, String name, Date start, Date end) { 22 | Page pager = new Page<>(page, count); 23 | IPage iPage = this.baseMapper.findLogsByUsernameAndRange(pager, name, start, end); 24 | return iPage; 25 | } 26 | 27 | @Override 28 | public IPage searchLogs(Long page, Long count, String name, String keyword, Date start, Date end) { 29 | Page pager = new Page<>(page, count); 30 | IPage iPage = this.baseMapper.searchLogsByUsernameAndKeywordAndRange(pager, name, "%" + keyword + "%", start, end); 31 | return iPage; 32 | } 33 | 34 | @Override 35 | public IPage getUserNames(Long page, Long count) { 36 | Page pager = new Page<>(page, count); 37 | IPage iPage = this.baseMapper.getUserNames(pager); 38 | return iPage; 39 | } 40 | 41 | @Override 42 | public boolean createLog(String message, String permission, Long userId, String username, String method, String path, Integer status) { 43 | LogDO record = LogDO.builder() 44 | .message(message) 45 | .userId(userId) 46 | .username(username) 47 | .statusCode(status) 48 | .method(method) 49 | .path(path) 50 | .build(); 51 | if (permission != null) { 52 | record.setPermission(permission); 53 | } 54 | return this.baseMapper.insert(record) > 0; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /demo/src/main/resources/mapper/LogMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 31 | 32 | 46 | 47 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /demo/src/main/resources/mapper/GroupMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 32 | 33 | 48 | 49 | 52 | 53 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/src/main/java/com/lin/cms/exception/HttpException.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.exception; 2 | 3 | import com.lin.cms.beans.Code; 4 | import com.lin.cms.interfaces.BaseResponse; 5 | import org.springframework.http.HttpStatus; 6 | 7 | /** 8 | * HttpException 异常类 9 | * 含异常信息 message 10 | * http状态码 httpCode 11 | * 错误码 code 12 | */ 13 | public class HttpException extends RuntimeException implements BaseResponse { 14 | 15 | private static final long serialVersionUID = 2359767895161832954L; 16 | 17 | protected int httpCode = HttpStatus.INTERNAL_SERVER_ERROR.value(); 18 | 19 | protected int code = Code.INTERNAL_SERVER_ERROR.getCode(); 20 | 21 | public HttpException() { 22 | super(Code.INTERNAL_SERVER_ERROR.getDescription()); 23 | } 24 | 25 | public HttpException(String message) { 26 | super(message); 27 | } 28 | 29 | public HttpException(int code) { 30 | super(Code.INTERNAL_SERVER_ERROR.getDescription()); 31 | this.code = code; 32 | } 33 | 34 | public HttpException(int code, int httpCode) { 35 | super(Code.INTERNAL_SERVER_ERROR.getDescription()); 36 | this.httpCode = httpCode; 37 | this.code = code; 38 | } 39 | 40 | public HttpException(String message, int code) { 41 | super(message); 42 | this.code = code; 43 | } 44 | 45 | public HttpException(String message, int code, int httpCode) { 46 | super(message); 47 | this.httpCode = httpCode; 48 | this.code = code; 49 | } 50 | 51 | public HttpException(Throwable cause, int code) { 52 | super(cause); 53 | this.code = code; 54 | } 55 | 56 | public HttpException(Throwable cause, int code, int httpCode) { 57 | super(cause); 58 | this.code = code; 59 | this.httpCode = httpCode; 60 | } 61 | 62 | public HttpException(String message, Throwable cause) { 63 | super(message, cause); 64 | } 65 | 66 | /** 67 | * for better performance 68 | * 69 | * @return Throwable 70 | */ 71 | // @Override 72 | // public Throwable fillInStackTrace() { 73 | // return this; 74 | // } 75 | public Throwable doFillInStackTrace() { 76 | return super.fillInStackTrace(); 77 | } 78 | 79 | public int getHttpCode() { 80 | return this.httpCode; 81 | } 82 | 83 | public int getCode() { 84 | return this.code; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/service/UserIdentityService.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.service; 2 | 3 | import com.lin.cms.demo.model.UserIdentityDO; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | 6 | /** 7 | * @author pedro 8 | * @since 2019-12-02 9 | */ 10 | public interface UserIdentityService extends IService { 11 | 12 | /** 13 | * 新建用户认证信息 14 | * 15 | * @param userId 用户id 16 | * @param identityType 认证类型 17 | * @param identifier 标识 18 | * @param credential 凭证 19 | * @return 用户认证 20 | */ 21 | UserIdentityDO createIdentity(Long userId, 22 | String identityType, 23 | String identifier, 24 | String credential); 25 | 26 | /** 27 | * 新建用户认证信息 28 | * 29 | * @param userIdentity 用户认证信息 30 | * @return 用户认证 31 | */ 32 | UserIdentityDO createIdentity(UserIdentityDO userIdentity); 33 | 34 | /** 35 | * 新建用户认证信息 (USERNAME_PASSWORD) 36 | * 37 | * @param userId 用户id 38 | * @param username 用户名 39 | * @param password 密码 40 | * @return 用户认证 41 | */ 42 | UserIdentityDO createUsernamePasswordIdentity(Long userId, 43 | String username, 44 | String password); 45 | 46 | 47 | /** 48 | * 验证用户认证信息 (USERNAME_PASSWORD) 49 | * 50 | * @param userId 用户id 51 | * @param username 用户名 52 | * @param password 密码 53 | * @return 是否验证成功 54 | */ 55 | boolean verifyUsernamePassword(Long userId, String username, String password); 56 | 57 | /** 58 | * 修改密码 59 | * 60 | * @param userId 用户id 61 | * @param password 新密码 62 | * @return 是否成功 63 | */ 64 | boolean changePassword(Long userId, String password); 65 | 66 | /** 67 | * 修改用户名 68 | * 69 | * @param userId 用户id 70 | * @param username 新用户名 71 | * @return 是否成功 72 | */ 73 | boolean changeUsername(Long userId, String username); 74 | 75 | /** 76 | * 修改用户名密码 77 | * 78 | * @param userId 用户id 79 | * @param username 新用户名 80 | * @param password 新密码 81 | * @return 是否成功 82 | */ 83 | boolean changeUsernamePassword(Long userId, String username, String password); 84 | } 85 | -------------------------------------------------------------------------------- /demo/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | logback 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 15 | 16 | 18 | 19 | 20 | 21 | debug 22 | 23 | 24 | ${CONSOLE_LOG_PATTERN} 25 | UTF-8 26 | 27 | 28 | 29 | 30 | 31 | 32 | ${log.path} 33 | 5MB 34 | 35 | 36 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 37 | UTF-8 38 | 39 | 40 | 41 | debug 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.lin.cms 7 | core 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | lin-cms-core 12 | core library for lin-cms 13 | 14 | 15 | UTF-8 16 | UTF-8 17 | 1.8 18 | 1.8 19 | 1.8 20 | 21 | 22 | 23 | 24 | jcenter 25 | https://jcenter.bintray.com/ 26 | 27 | 28 | 29 | 30 | 31 | 32 | com.auth0 33 | java-jwt 34 | 3.8.0 35 | 36 | 37 | 38 | com.amdelamar 39 | jhash 40 | 2.1.2 41 | 42 | 43 | 44 | ch.qos.logback 45 | logback-core 46 | 1.2.3 47 | 48 | 49 | 50 | ch.qos.logback 51 | logback-classic 52 | 1.2.3 53 | test 54 | 55 | 56 | 57 | org.slf4j 58 | slf4j-api 59 | 1.7.25 60 | test 61 | 62 | 63 | 64 | org.projectlombok 65 | lombok 66 | 1.18.6 67 | provided 68 | 69 | 70 | 71 | junit 72 | junit 73 | 4.12 74 | compile 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /core/src/test/java/com/lin/cms/core/token/SingleJWTTest.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.core.token; 2 | 3 | import com.auth0.jwt.algorithms.Algorithm; 4 | import com.auth0.jwt.interfaces.Claim; 5 | import com.lin.cms.core.utils.DateUtil; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.junit.Test; 8 | 9 | import java.util.Map; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | @Slf4j 14 | public class SingleJWTTest { 15 | 16 | @Test 17 | public void generateToken() { 18 | SingleJWT jwt = new SingleJWT("secret", 1000); 19 | String token = jwt.generateToken("test", 1, "test", 1000); 20 | assertNotNull(token); 21 | log.info(token); 22 | } 23 | 24 | @Test 25 | public void getSpecifyToken() { 26 | SingleJWT jwt = new SingleJWT("secret", 1000); 27 | String token = jwt.getBuilder() 28 | .withClaim("type", "test") 29 | .withClaim("identity", 1) 30 | .withClaim("scope", "test") 31 | .withExpiresAt(DateUtil.getDurationDate(10000)) 32 | .sign(jwt.getAlgorithm()); 33 | assertNotNull(token); 34 | log.info(token); 35 | } 36 | 37 | @Test 38 | public void decodeToken() { 39 | SingleJWT jwt = new SingleJWT("secret", 10000); 40 | String token = jwt.getBuilder() 41 | .withClaim("type", "test") 42 | .withClaim("identity", 1) 43 | .withClaim("scope", "test") 44 | .withExpiresAt(DateUtil.getDurationDate(10000)) 45 | .sign(jwt.getAlgorithm()); 46 | assertNotNull(token); 47 | log.info(token); 48 | Map claimMap = jwt.decodeToken(token); 49 | log.info("{}", claimMap); 50 | assertEquals(claimMap.get("type").asString(), "test"); 51 | assertEquals(claimMap.get("scope").asString(), "test"); 52 | } 53 | 54 | @Test 55 | public void getVerifier() { 56 | Algorithm algorithm = Algorithm.HMAC256("secret"); 57 | SingleJWT jwt = new SingleJWT(algorithm, 1000); 58 | assertNotNull(jwt.getVerifier()); 59 | } 60 | 61 | @Test 62 | public void getBuilder() { 63 | Algorithm algorithm = Algorithm.HMAC256("secret"); 64 | SingleJWT jwt = new SingleJWT(algorithm, 1000); 65 | assertNotNull(jwt.getBuilder()); 66 | } 67 | 68 | @Test 69 | public void getAlgorithm() { 70 | Algorithm algorithm = Algorithm.HMAC256("secret"); 71 | SingleJWT jwt = new SingleJWT(algorithm, 1000); 72 | assertNotNull(jwt.getAlgorithm()); 73 | } 74 | 75 | @Test 76 | public void getExpire() { 77 | Algorithm algorithm = Algorithm.HMAC256("secret"); 78 | SingleJWT jwt = new SingleJWT(algorithm, 1000); 79 | assertTrue(jwt.getExpire() == 1000L); 80 | } 81 | } -------------------------------------------------------------------------------- /lin-cms-spring-boot-autoconfigure/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.2.2.RELEASE 10 | 11 | 12 | 13 | com.lin.cms.autoconfigure 14 | lin-cms-spring-boot-autoconfigure 15 | 0.0.1-SNAPSHOT 16 | lin-cms-spring-boot-autoconfigure 17 | autoconfigure project for lin cms 18 | jar 19 | 20 | 21 | UTF-8 22 | UTF-8 23 | 1.8 24 | 1.8 25 | 1.8 26 | 27 | 28 | 29 | 30 | com.lin.cms 31 | core 32 | 0.0.1-SNAPSHOT 33 | 34 | 35 | 36 | org.projectlombok 37 | lombok 38 | 1.18.6 39 | provided 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-autoconfigure 45 | 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter 50 | provided 51 | 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-web 56 | provided 57 | 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-starter-test 62 | test 63 | 64 | 65 | 66 | org.springframework.boot 67 | spring-boot-configuration-processor 68 | true 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /demo/src/test/java/com/lin/cms/demo/mapper/LogMapperTest.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.lin.cms.demo.common.mybatis.Page; 5 | import com.lin.cms.demo.model.LogDO; 6 | import org.junit.After; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.test.annotation.Rollback; 13 | import org.springframework.test.context.ActiveProfiles; 14 | import org.springframework.test.context.junit4.SpringRunner; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | import java.util.Date; 18 | import java.util.List; 19 | 20 | import static org.junit.Assert.*; 21 | 22 | @RunWith(SpringRunner.class) 23 | @SpringBootTest 24 | @Transactional 25 | @Rollback 26 | @ActiveProfiles("test") 27 | public class LogMapperTest { 28 | 29 | 30 | @Autowired 31 | private LogMapper logMapper; 32 | 33 | private Date start = new Date(); 34 | private String permission = "查看lin的信息"; 35 | private String message = "就是个瓜皮"; 36 | private String method = "GET"; 37 | private String path = "/"; 38 | private Integer statusCode = 200; 39 | private Long userId = 1L; 40 | private String username = "super"; 41 | 42 | @Before 43 | public void setUp() throws Exception { 44 | LogDO logDO = new LogDO(); 45 | logDO.setPermission(permission); 46 | logDO.setMessage(message); 47 | logDO.setMethod(method); 48 | logDO.setPath(path); 49 | logDO.setStatusCode(statusCode); 50 | logDO.setUserId(userId); 51 | logDO.setUsername(username); 52 | logDO.setCreateTime(start); 53 | long ll = start.getTime() - 500000; 54 | // start.setTime(ll); 55 | start = new Date(ll); 56 | logMapper.insert(logDO); 57 | } 58 | 59 | @Test 60 | public void testFindLogsByUsernameAndRange() { 61 | Date now = new Date(); 62 | Page page = new Page(0, 10); 63 | IPage iPage = logMapper.findLogsByUsernameAndRange(page, username, start, now); 64 | List logs = iPage.getRecords(); 65 | assertTrue(logs.size() > 0); 66 | } 67 | 68 | @Test 69 | public void testFindLogsByUsernameAndRange1() { 70 | long changed = start.getTime(); 71 | Date ch = new Date(changed - 1000); 72 | Date ch1 = new Date(changed - 2000); 73 | Page page = new Page(1, 10); 74 | IPage iPage = logMapper.findLogsByUsernameAndRange(page, username, ch1, ch); 75 | List logs = iPage.getRecords(); 76 | assertTrue(logs.size() == 0); 77 | } 78 | 79 | @After 80 | public void tearDown() throws Exception { 81 | } 82 | } -------------------------------------------------------------------------------- /demo/src/test/java/com/lin/cms/demo/mapper/UserMapperTest.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.lin.cms.demo.common.mybatis.Page; 5 | import com.lin.cms.demo.model.GroupDO; 6 | import com.lin.cms.demo.model.UserDO; 7 | import com.lin.cms.demo.model.UserGroupDO; 8 | import org.junit.After; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.test.annotation.Rollback; 15 | import org.springframework.test.context.ActiveProfiles; 16 | import org.springframework.test.context.junit4.SpringRunner; 17 | import org.springframework.transaction.annotation.Transactional; 18 | 19 | 20 | import static org.junit.Assert.*; 21 | 22 | @RunWith(SpringRunner.class) 23 | @SpringBootTest 24 | @Transactional 25 | @Rollback 26 | @ActiveProfiles("test") 27 | public class UserMapperTest { 28 | 29 | @Autowired 30 | private UserMapper userMapper; 31 | 32 | @Autowired 33 | private GroupMapper groupMapper; 34 | 35 | @Autowired 36 | private UserGroupMapper userGroupMapper; 37 | 38 | 39 | @Test 40 | public void selectCountByUsername() { 41 | String email = "13129982604@qq.com"; 42 | String username = "pedro-test"; 43 | UserDO userDO = new UserDO(); 44 | userDO.setEmail(email); 45 | userDO.setUsername(username); 46 | userMapper.insert(userDO); 47 | int count = userMapper.selectCountByUsername(username); 48 | assertTrue(count > 0); 49 | } 50 | 51 | @Test 52 | public void selectCountById() { 53 | String email = "13129982604@qq.com"; 54 | String username = "pedro-test"; 55 | UserDO userDO = new UserDO(); 56 | userDO.setEmail(email); 57 | userDO.setUsername(username); 58 | userMapper.insert(userDO); 59 | int count = userMapper.selectCountById(userDO.getId()); 60 | assertTrue(count > 0); 61 | } 62 | 63 | @Test 64 | public void selectPageByGroupId() { 65 | String email = "13129982604@qq.com"; 66 | String username = "pedro-test"; 67 | UserDO userDO = new UserDO(); 68 | userDO.setEmail(email); 69 | userDO.setUsername(username); 70 | userMapper.insert(userDO); 71 | 72 | GroupDO group = GroupDO.builder().name("group").info("零零落落").build(); 73 | groupMapper.insert(group); 74 | 75 | userGroupMapper.insert(new UserGroupDO(userDO.getId(), group.getId())); 76 | 77 | Page page = new Page(0, 10); 78 | IPage iPage = userMapper.selectPageByGroupId(page, group.getId()); 79 | assertTrue(iPage.getTotal() > 0); 80 | boolean anyMatch = iPage.getRecords().stream().anyMatch(it -> it.getUsername().equals(username)); 81 | assertTrue(anyMatch); 82 | } 83 | } -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/service/impl/FileServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.service.impl; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import cn.hutool.core.io.FileUtil; 5 | import com.lin.cms.demo.bo.FileBO; 6 | import com.lin.cms.demo.extensions.file.File; 7 | import com.lin.cms.demo.extensions.file.FileConsts; 8 | import com.lin.cms.demo.extensions.file.Uploader; 9 | import com.lin.cms.demo.mapper.FileMapper; 10 | import com.lin.cms.demo.model.FileDO; 11 | import com.lin.cms.demo.service.FileService; 12 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.beans.factory.annotation.Value; 15 | import org.springframework.stereotype.Service; 16 | import org.springframework.util.MultiValueMap; 17 | import org.springframework.web.multipart.MultipartFile; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.stream.Collectors; 22 | 23 | /** 24 | * @author pedro 25 | * @since 2019-11-30 26 | */ 27 | @Service 28 | public class FileServiceImpl extends ServiceImpl implements FileService { 29 | 30 | @Autowired 31 | private Uploader uploader; 32 | 33 | @Value("${lin.cms.file.domain}") 34 | private String domain; 35 | 36 | @Value("${lin.cms.file.store-dir:assets/}") 37 | private String dir; 38 | 39 | /** 40 | * 为什么不做批量插入 41 | * 1. 文件上传的数量一般不多,3个左右 42 | * 2. 批量插入不能得到数据的id字段,不利于直接返回数据 43 | * 3. 批量插入也仅仅只是一条sql语句的事情,如果真的需要,可以自行尝试一下 44 | */ 45 | @Override 46 | public List upload(MultiValueMap fileMap) { 47 | List tmp = new ArrayList<>(); 48 | List files = uploader.upload(fileMap, file -> { 49 | FileDO found = this.baseMapper.selectByMd5(file.getMd5()); 50 | if (found == null) 51 | return true; 52 | tmp.add(found); 53 | return false; 54 | }); 55 | tmp.addAll(files.stream().map(file -> { 56 | FileDO fileDO = new FileDO(); 57 | BeanUtil.copyProperties(file, fileDO); 58 | this.getBaseMapper().insert(fileDO); 59 | return fileDO; 60 | }).collect(Collectors.toList())); 61 | 62 | List res = tmp.stream().map(file -> { 63 | FileBO bo = new FileBO(); 64 | BeanUtil.copyProperties(file, bo); 65 | if (file.getType().equals(FileConsts.LOCAL)) { 66 | String s = FileUtil.mainName(dir); 67 | bo.setPath(domain + s + "/" + file.getName()); 68 | } 69 | return bo; 70 | }).collect(Collectors.toList()); 71 | return res; 72 | } 73 | 74 | @Override 75 | public boolean checkFileExistByMd5(String md5) { 76 | return this.getBaseMapper().selectCountByMd5(md5) > 0; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/controller/v1/BookController.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.controller.v1; 2 | 3 | import com.lin.cms.demo.model.BookDO; 4 | import com.lin.cms.demo.service.BookService; 5 | import com.lin.cms.demo.dto.book.CreateOrUpdateBookDTO; 6 | import com.lin.cms.core.annotation.GroupRequired; 7 | import com.lin.cms.core.annotation.RouteMeta; 8 | import com.lin.cms.demo.vo.UnifyResponseVO; 9 | import com.lin.cms.exception.NotFoundException; 10 | import com.lin.cms.demo.common.utils.ResponseUtil; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.validation.annotation.Validated; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | import javax.validation.constraints.Positive; 16 | import java.util.List; 17 | 18 | @RestController 19 | @RequestMapping("/v1/book") 20 | @Validated 21 | public class BookController { 22 | 23 | @Autowired 24 | private BookService bookService; 25 | 26 | @GetMapping("/{id}") 27 | public BookDO getBook(@PathVariable(value = "id") @Positive(message = "{id}") Long id) { 28 | BookDO book = bookService.getById(id); 29 | if (book == null) { 30 | throw new NotFoundException("book not found", 10022); 31 | } 32 | return book; 33 | } 34 | 35 | @GetMapping("") 36 | public List getBooks() { 37 | List books = bookService.findAll(); 38 | return books; 39 | } 40 | 41 | 42 | @GetMapping("/search") 43 | public List searchBook(@RequestParam(value = "q", required = false, defaultValue = "") String q) { 44 | List books = bookService.getBookByKeyword("%" + q + "%"); 45 | return books; 46 | } 47 | 48 | 49 | @PostMapping("") 50 | public UnifyResponseVO createBook(@RequestBody @Validated CreateOrUpdateBookDTO validator) { 51 | bookService.createBook(validator); 52 | return ResponseUtil.generateUnifyResponse(10); 53 | } 54 | 55 | 56 | @PutMapping("/{id}") 57 | public UnifyResponseVO updateBook(@PathVariable("id") @Positive(message = "{id}") Long id, @RequestBody @Validated CreateOrUpdateBookDTO validator) { 58 | BookDO book = bookService.getById(id); 59 | if (book == null) { 60 | throw new NotFoundException("book not found", 10022); 61 | } 62 | bookService.updateBook(book, validator); 63 | return ResponseUtil.generateUnifyResponse(11); 64 | } 65 | 66 | 67 | @DeleteMapping("/{id}") 68 | @RouteMeta(permission = "删除图书", module = "图书", mount = true) 69 | @GroupRequired 70 | public UnifyResponseVO deleteBook(@PathVariable("id") @Positive(message = "{id}") Long id) { 71 | BookDO book = bookService.getById(id); 72 | if (book == null) { 73 | throw new NotFoundException("book not found", 10022); 74 | } 75 | bookService.deleteById(book.getId()); 76 | return ResponseUtil.generateUnifyResponse(12); 77 | } 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.lin.cms.demo.common.mybatis.Page; 5 | import com.lin.cms.demo.dto.user.ChangePasswordDTO; 6 | import com.lin.cms.demo.dto.user.RegisterDTO; 7 | import com.lin.cms.demo.dto.user.UpdateInfoDTO; 8 | import com.baomidou.mybatisplus.extension.service.IService; 9 | import com.lin.cms.demo.model.GroupDO; 10 | import com.lin.cms.demo.model.PermissionDO; 11 | import com.lin.cms.demo.model.UserDO; 12 | 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * 用户业务 18 | * 19 | * @author pedro 20 | * @since 2019-11-30 21 | */ 22 | public interface UserService extends IService { 23 | 24 | /** 25 | * 新建用户 26 | * 27 | * @param validator 新建用户校验器 28 | * @return 被创建的用户 29 | */ 30 | UserDO createUser(RegisterDTO validator); 31 | 32 | /** 33 | * 更新用户 34 | * 35 | * @param validator 更新用户信息用户校验器 36 | * @return 被更新的用户 37 | */ 38 | UserDO updateUserInfo(UpdateInfoDTO validator); 39 | 40 | /** 41 | * 修改用户密码 42 | * 43 | * @param validator 修改密码校验器 44 | * @return 被修改密码的用户 45 | */ 46 | UserDO changeUserPassword(ChangePasswordDTO validator); 47 | 48 | /** 49 | * 获得用户的所有分组 50 | * 51 | * @param userId 用户id 52 | * @return 所有分组 53 | */ 54 | List getUserGroups(Long userId); 55 | 56 | /** 57 | * 获得用户所有权限 58 | * 59 | * @param userId 用户id 60 | * @return 权限 61 | */ 62 | List>>> getStructualUserPermissions(Long userId); 63 | 64 | /** 65 | * 获得用户所有权限 66 | * 67 | * @param userId 用户id 68 | * @return 权限 69 | */ 70 | List getUserPermissions(Long userId); 71 | 72 | 73 | /** 74 | * 通过用户名查找用户 75 | * 76 | * @param username 用户名 77 | * @return 用户 78 | */ 79 | UserDO getUserByUsername(String username); 80 | 81 | /** 82 | * 根据用户名检查用户是否存在 83 | * 84 | * @param username 用户名 85 | * @return true代表存在 86 | */ 87 | boolean checkUserExistByUsername(String username); 88 | 89 | 90 | /** 91 | * 根据用户名检查用户是否存在 92 | * 93 | * @param email 邮箱 94 | * @return true代表存在 95 | */ 96 | boolean checkUserExistByEmail(String email); 97 | 98 | /** 99 | * 根据用户id检查用户是否存在 100 | * 101 | * @param id 用户名 102 | * @return true代表存在 103 | */ 104 | boolean checkUserExistById(Long id); 105 | 106 | /** 107 | * 根据分组id分页获取用户数据 108 | * 109 | * @param pager 分页 110 | * @param groupId 分组id 111 | * @return 数据页 112 | */ 113 | IPage getUserPageByGroupId(Page pager, Long groupId); 114 | } 115 | -------------------------------------------------------------------------------- /docs/structure.md: -------------------------------------------------------------------------------- 1 | # 架构 2 | 3 | ## 前言 4 | 5 | 在正文之前,笔者觉得还是很有必要来唠唠嗑的。一谈到架构这个问题,很多人些许有些慌 6 | 了,是不是很难啊?是不是很复杂啊?是不是得精通各种技术才能学啊? 7 | 8 | 当然不是,架构不是具体实现。你只需要有一点想象力,有一点理解能力,另外还有一点看 9 | 图能力,你就能大概的理解本章的内容。 10 | 11 | 当然了,如果你专注于开发,啪啦啪啦的写代码完全可以略过本节,因为本小节没有代码; 12 | 如果你想更好的开发、更有计划的开发甚至想参与lin-cms的开发,那么本节是你必不可少 13 | 的一节。 14 | 15 | ## 结构 16 | 17 | `架构`这个词或许显得有些高大上,所以我们先以`结构`这个词来理解理解。一般地,我们 18 | 可以把结构大致的分为两类——即水平结构和垂直结构。 19 | 20 | 这样就很好理解了,一个产品的落地可能是水平结构,可能是垂直结构,当然了,大概率还 21 | 是水平+垂直的结构。 22 | 23 | `lin-cms`是鲜明的`垂直结构`,且被分为了三层,`core`,`starter`和`demo`。 24 | 25 | ```bash 26 | -------- ----------- ---------- 27 | | | | | | | 28 | | core | ---> | starter | ---> | demo | 29 | | | | | | | 30 | -------- ----------- ---------- 31 | ``` 32 | 33 | 不要被上面的图所误导,虽然它是横着画的,可是它确实是从最底层的`core`一直通过箭头 34 | 向上推进到`demo`的,所以它确实是垂直结构。 35 | 36 | 而且这三层是上下依赖的,即`demo`依赖`starter`,`starter`依赖`core`。 37 | 38 | 说了那么多,究竟什么是`demo`和`starter`啊?先卖个关子,如果你对lin-cms的开发足够 39 | 熟悉可以先琢磨琢磨。答案嘛,别急,在下面! 40 | 41 | ## 边界 42 | 43 | 既然谈到`分层`,那就必须得谈到另一个概念——`边界`。例如,core 与 starter 这两层是 44 | 被什么分开的,当然是被边界分开的。 45 | 46 | 那么`lin-cms`的结构必然会存在两个边界,分别用来分开`core`与`starter`以及 47 | `starter`与`demo`。这两个边界如下: 48 | 49 | - lin 边界。我们在 core 中封装了一些可复用的类库,这些类库不仅可以帮 50 | 助`starter`层进行快速的开发,还能帮助其它的 java 项目进行开发。这样的类库 51 | 需放在 core 里面,它可以服务于其它的项目。我们以 lin 为边界来区分,把可复用 52 | 的类库放到了 core 中,把跟 lin 相关的放在了 starter 中。 53 | 如果你熟悉spring-boot,那么一定能理解`starter`,没错,这里的starter就是 54 | spring-boot的starter,它提供了自动装配和配置信息。 55 | 56 | - 业务边界。`starter`提供了配置和具体的业务结构,但starter 中不存放任何与 57 | 具体业务相关的代码,应该把业务代码存放在 demo中。starter 中应是 58 | 业务的结构和逻辑,而将具体的业务实现交给 demo。 59 | 60 | 好,你可能已经蒙圈了。没关系,你只需要知道,我们的三层结构是通过两个边界区分的, 61 | 这两个边界分别是`与lin有无关系`,`与业务有无关系`。 62 | 63 | 知道了边界以后,我们再来摆出`core`、`starter`和`demo`的定义。 64 | 65 | - `core`:全名lin-cms-core,其实就是一堆基础类库,方便我们进行业务开发。 66 | - `starter`:全名lin-cms-starter,其实就是spring-boot里面的starter,提供自动装配和默认配置,但不涉及具体业务的实现。 67 | - `demo`:全名lin-cms-java,也就是我们的工程项目。 68 | 69 | 70 | ## 功能点 71 | 72 | 下面我们将会概括性的介绍一下`core`与`starter`的功能。 73 | 74 | ### core 75 | 76 | 代码目录如下: 77 | 78 | ```bash 79 | core/src/main/java/com/lin/cms/core/ 80 | ├── annotation 注解类 81 | ├── consts 常量类 82 | ├── enums 枚举类 83 | ├── logger 日志 84 | ├── token 令牌 85 | └── utils 工具函数类 86 | ``` 87 | 88 | 在文件夹的后方,我们已经标注了其作用。 89 | 90 | 目前来说,core的功能其实不多,主要如下: 91 | 92 | 1. 定义了lin-cms里面需要的注解和枚举。 93 | 2. 自定义logback appender类,方便实现文件日志记录。 94 | 3. 提供了令牌类库,方便使用JWT。 95 | 96 | ### starter 97 | 98 | 代码目录如下: 99 | 100 | ```bash 101 | ├── autoconfigure 自动装配类 102 | ├── beans java bean,全局信息类 103 | ├── exception 异常类 104 | ├── interceptor spring-mvc 拦截器 105 | ├── interfaces 抽象接口 106 | ├── response 统一相应类 107 | ├── utils 工具类 108 | └── validator 校验类 109 | ``` 110 | 111 | 在文件夹的后方,我们也已经标注了其作用。 112 | 113 | starter 的主要功能如下: 114 | 115 | 1. 提供自动配置装配 116 | 2. 提供常见异常类,方便在工程中直接使用。 117 | 3. 提供抽象接口,供工程项目实现。 118 | 4. 提供校验类,供工程项目使用。 119 | 120 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/common/configure/CommonConfig.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.common.configure; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; 5 | import com.baomidou.mybatisplus.core.injector.ISqlInjector; 6 | import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; 7 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 8 | import com.lin.cms.beans.RouteMetaCollector; 9 | import com.lin.cms.demo.extensions.file.FileProperties; 10 | import com.lin.cms.demo.common.interceptor.RequestLogInterceptor; 11 | import com.lin.cms.demo.model.PermissionDO; 12 | import com.lin.cms.demo.service.PermissionService; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; 15 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.Configuration; 18 | 19 | 20 | @Configuration(proxyBeanMethods = false) 21 | @EnableConfigurationProperties(FileProperties.class) 22 | public class CommonConfig { 23 | 24 | @Autowired 25 | private PermissionService permissionService; 26 | 27 | @Bean 28 | public RequestLogInterceptor requestLogInterceptor() { 29 | return new RequestLogInterceptor(); 30 | } 31 | 32 | @Bean 33 | public PaginationInterceptor paginationInterceptor() { 34 | return new PaginationInterceptor(); 35 | } 36 | 37 | @Bean 38 | public ISqlInjector sqlInjector() { 39 | return new DefaultSqlInjector(); 40 | } 41 | 42 | 43 | /** 44 | * 记录每个被 @RouteMeta 记录的信息,在beans的后置调用 45 | * 46 | * @return RouteMetaCollector 47 | */ 48 | @Bean 49 | public RouteMetaCollector postProcessBeans() { 50 | return new RouteMetaCollector(meta -> { 51 | if (meta.mount()) { 52 | String module = meta.module(); 53 | String permission = meta.permission(); 54 | QueryWrapper wrapper = new QueryWrapper<>(); 55 | wrapper.lambda().eq(PermissionDO::getName, permission).eq(PermissionDO::getModule, module); 56 | PermissionDO one = permissionService.getOne(wrapper); 57 | if (one == null) { 58 | permissionService.save(PermissionDO.builder().module(module).name(permission).build()); 59 | } 60 | } 61 | }); 62 | } 63 | 64 | 65 | /** 66 | * 接口中,自动转换的有:驼峰转换为下划线,空值输出null 67 | */ 68 | @Bean 69 | public Jackson2ObjectMapperBuilderCustomizer customJackson() { 70 | return jacksonObjectMapperBuilder -> { 71 | // jacksonObjectMapperBuilder.serializationInclusion(JsonInclude.Include.NON_NULL); 72 | jacksonObjectMapperBuilder.failOnUnknownProperties(false); 73 | jacksonObjectMapperBuilder.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); 74 | }; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /demo/src/main/java/com/lin/cms/demo/common/interceptor/LoggerImpl.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.demo.common.interceptor; 2 | 3 | import com.lin.cms.demo.common.LocalUser; 4 | import com.lin.cms.demo.model.UserDO; 5 | import com.lin.cms.interfaces.LoggerResolver; 6 | import com.lin.cms.core.annotation.Logger; 7 | import com.lin.cms.core.annotation.RouteMeta; 8 | import com.lin.cms.core.utils.BeanUtil; 9 | import com.lin.cms.demo.service.LogService; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Component; 13 | 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.util.regex.Matcher; 17 | import java.util.regex.Pattern; 18 | 19 | @Slf4j 20 | @Component 21 | public class LoggerImpl implements LoggerResolver { 22 | 23 | @Autowired 24 | private LogService logService; 25 | 26 | public static String REG_XP = "(?<=\\{)[^}]*(?=\\})"; 27 | 28 | private Pattern pattern = Pattern.compile(REG_XP); 29 | 30 | 31 | @Override 32 | public void handle(RouteMeta meta, Logger logger, HttpServletRequest request, HttpServletResponse response) { 33 | // parse template and extract properties from request,response and modelAndView 34 | String template = logger.template(); 35 | UserDO user = LocalUser.getLocalUser(); 36 | template = this.parseTemplate(template, user, request, response); 37 | String permission = meta.permission(); 38 | Long userId = user.getId(); 39 | String userName = user.getNickname(); 40 | String method = request.getMethod(); 41 | String path = request.getServletPath(); 42 | Integer status = response.getStatus(); 43 | logService.createLog(template, permission, userId, userName, method, path, status); 44 | } 45 | 46 | private String parseTemplate(String template, UserDO user, HttpServletRequest request, HttpServletResponse response) { 47 | // 调用 get 方法 48 | Matcher m = pattern.matcher(template); 49 | while (m.find()) { 50 | String group = m.group(); 51 | String property = this.extractProperty(group, user, request, response); 52 | template = template.replace("{" + group + "}", property); 53 | } 54 | return template; 55 | } 56 | 57 | private String extractProperty(String item, UserDO user, HttpServletRequest request, HttpServletResponse response) { 58 | int i = item.lastIndexOf('.'); 59 | String obj = item.substring(0, i); 60 | String prop = item.substring(i + 1); 61 | switch (obj) { 62 | case "user": 63 | if (user == null) { 64 | return ""; 65 | } 66 | return BeanUtil.getValueByPropName(user, prop); 67 | case "request": 68 | return BeanUtil.getValueByPropName(request, prop); 69 | case "response": 70 | return BeanUtil.getValueByPropName(response, prop); 71 | default: 72 | return ""; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /core/src/main/java/com/lin/cms/core/token/SingleJWT.java: -------------------------------------------------------------------------------- 1 | package com.lin.cms.core.token; 2 | 3 | import com.auth0.jwt.JWTCreator; 4 | import com.auth0.jwt.JWTVerifier; 5 | import com.auth0.jwt.algorithms.Algorithm; 6 | import com.auth0.jwt.exceptions.TokenExpiredException; 7 | import com.auth0.jwt.interfaces.Claim; 8 | import com.auth0.jwt.interfaces.DecodedJWT; 9 | import com.lin.cms.core.utils.DateUtil; 10 | 11 | import java.util.Date; 12 | import java.util.Map; 13 | 14 | 15 | public class SingleJWT { 16 | 17 | private Algorithm algorithm; 18 | 19 | private long expire; 20 | 21 | 22 | private JWTVerifier verifier; 23 | 24 | private JWTCreator.Builder builder; 25 | 26 | /** 27 | * @param algorithm 加密算法 28 | * @param expire token过期时间 29 | */ 30 | public SingleJWT(Algorithm algorithm, long expire) { 31 | this.algorithm = algorithm; 32 | this.expire = expire; 33 | this.initBuilderAndVerifier(); 34 | } 35 | 36 | /** 37 | * @param secret 不传入加密算法,传入密钥,则默认使用 HMAC256 加密算法 38 | * @param expire token过期时间 39 | */ 40 | public SingleJWT(String secret, long expire) { 41 | this.algorithm = Algorithm.HMAC256(secret); 42 | this.expire = expire; 43 | this.initBuilderAndVerifier(); 44 | } 45 | 46 | public String generateToken(String tokenType, long identity, String scope, long expire) { 47 | Date expireDate = DateUtil.getDurationDate(expire); 48 | return builder 49 | .withClaim("type", tokenType) 50 | .withClaim("identity", identity) 51 | .withClaim("scope", scope) 52 | .withExpiresAt(expireDate) 53 | .sign(algorithm); 54 | } 55 | 56 | public Map decodeToken(String token) { 57 | DecodedJWT jwt = verifier.verify(token); 58 | checkTokenExpired(jwt.getExpiresAt()); 59 | return jwt.getClaims(); 60 | } 61 | 62 | private void checkTokenExpired(Date expiresAt) { 63 | long now = new Date().getTime(); 64 | if (expiresAt.getTime() < now) { 65 | throw new TokenExpiredException("token is expired"); 66 | } 67 | } 68 | 69 | /*** 70 | * 获得令牌的验证器 71 | * @return JWTVerifier 72 | */ 73 | public JWTVerifier getVerifier() { 74 | return verifier; 75 | } 76 | 77 | /** 78 | * 获得令牌构建器 79 | * 80 | * @return Builder 81 | */ 82 | public JWTCreator.Builder getBuilder() { 83 | return builder; 84 | } 85 | 86 | /** 87 | * 获得加密方法 88 | * 89 | * @return Algorithm 90 | */ 91 | public Algorithm getAlgorithm() { 92 | return algorithm; 93 | } 94 | 95 | public Long getExpire() { 96 | return expire; 97 | } 98 | 99 | 100 | private void initBuilderAndVerifier() { 101 | verifier = com.auth0.jwt.JWT.require(algorithm) 102 | .acceptExpiresAt(this.expire) 103 | .build(); 104 | builder = com.auth0.jwt.JWT.create(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /docs/logging.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 日志系统 3 | --- 4 | 5 | # 日志系统 6 | 7 | lin-cms的日志系统基于spring-boot和logback,在此之上提供了日志记录文件和请求 8 | 日志记录两个功能。 9 | 10 | ## 使用 11 | 12 | lin-cms推荐使用lombok注解的方式去记录日志。 13 | 14 | 首先,请在需要进行日志记录的类上打上`Slf4j`注解,如下: 15 | 16 | ```java 17 | @Slf4j 18 | public class RequestLogInterceptor extends HandlerInterceptorAdapter { 19 | // 省略 20 | } 21 | ``` 22 | 23 | 当开启该注解后,便可以在类中方便的使用`log`进行日志记录: 24 | 25 | ``` 26 | log.info("[{}] -> [{}] from: {} costs: {}ms", 27 | request.getMethod(), 28 | request.getServletPath(), 29 | request.getRemoteAddr(), 30 | System.currentTimeMillis() - startTime.get() 31 | ); 32 | ``` 33 | 34 | logback日志共有五个等级,分别为`trace`,`debug`,`info`,`warn`和`error`,即: 35 | 36 | ``` 37 | log.trace(); 38 | log.info(); 39 | log.warn(); 40 | log.debug(); 41 | log.error(); 42 | ``` 43 | 44 | 请根据实际的日志等级调用正确的方法。 45 | 46 | lin-cms仅在spring-boot和logback的基础上,增加了一些必要的日志功能,因此保留了 47 | spring-boot的日志配置方式,如果你不熟悉spring-boot可以查阅一下它的文档。 48 | 49 | ## 日志记录 50 | 51 | lin-cms将日志会记录到终端和文件两个地方,在生产环境下只会向文件中记录。 52 | 记录日志的文件默认在工作目录下的`log`文件夹中,如下: 53 | 54 | ```bash 55 | logs 56 | └── 2020-01 57 | ├── 2020-01-06.log 58 | ├── 2020-01-09.log 59 | └── 2020-01-13.log 60 | ``` 61 | 62 | 文件以一个月作为一个子目录,每一个子目录下皆有每一天的日志文件。 63 | 当某一天日志文件超过一定的大小时,会被切割,默认的切割大小为`5M`。 64 | 65 | 当然你也可以通过修改resources目录下的`logback-spring.xml`文件来修改日志的记录方式。 66 | 67 | logback-spring.xml文件中的`log.path`属性可以指定日志文件的记录位置,如下: 68 | 69 | ```xml 70 | 71 | ``` 72 | 73 | 如果需要改变日志文件的存储位置,可以修改该属性(可以为绝对路径)达到你的目的。 74 | 75 | 76 | ## 日志配置 77 | 78 | `logback-spring.xml`文件是logback默认配置文件,你可以更换`appender`,修改 79 | 日志文件分割大小。其中几个重要的配置如下: 80 | 81 | ``` 82 | // 日志存储文件 83 | ``` 84 | 85 | ``` 86 | // 自定义日志appender 87 | ${log.path} // 日志存储位置 88 | 5MB // 日志切割大小,默认为5M 89 | // 日志记录格式 90 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 91 | UTF-8 // 文件默认编码 92 | 93 | 94 | debug // 日志过滤等级,默认为debug,即记录debug以上的等级 95 | 96 | 97 | ``` 98 | 99 | logback中,日志等级是一个较为重要的概念,`trace`,`debug`,`info`,`warn`和`error`的等级依次递增, 100 | 只要日志等级足够才会被记录,如上面的配置文件中指定了日志过滤等级为`debug`,则在`debug`之上的 101 | 等级日志会被记录,所以`trace`日志不会被记录。 102 | 103 | 大多数情况下,你只需要修改上面提到的几个配置就足够了,如果你需要定制自己的日志格式和 104 | 记录方式,可以查阅logback文档。 105 | 106 | 一般情况下,我们都推荐通过`application.properties`配置文件来改变日志记录等级,如下: 107 | 108 | ```properties 109 | # 日志等级 110 | logging.level.com.lin.cms.demo.mapper=debug 111 | logging.level.web=debug 112 | # 日志配置文件 113 | logging.config=classpath:logback-spring.xml 114 | # 是否开启请求日志记录 115 | request-log.enabled=true 116 | ``` 117 | 118 | `logging.level`可以指定日志记录等级,`com.lin.cms.demo.mapper`是包名,表示这个包下的所有 119 | 日志记录等级均为`debug`,你也可以采用这种方式来指定特定包的日志等级。 120 | 121 | `request-log.enabled`表示是否开启请求日志记录,默认是开,当然如果你不需要也可以关闭。 122 | 123 | 124 | :::tip 125 | 126 | 如果你使用lin-cms,我们还是建议你直接使用我们的日志模式,在application.properties配置相应包的 127 | 日志记录等级其实已经足够了。 128 | 129 | ::: --------------------------------------------------------------------------------