├── .github ├── ISSUE_TEMPLATE │ ├── question.md │ ├── feature_request.md │ └── bug_report.md ├── workflows │ └── maven.yml └── dependabot.yml ├── src ├── main │ ├── resources │ │ ├── DejaVuSerif-Bold.ttf │ │ ├── application-prod.yml │ │ ├── application-test.yml │ │ ├── mapper │ │ │ ├── UserIdentityMapper.xml │ │ │ ├── UserGroupMapper.xml │ │ │ ├── GroupPermissionMapper.xml │ │ │ ├── BookMapper.xml │ │ │ ├── FileMapper.xml │ │ │ ├── UserMapper.xml │ │ │ ├── LogMapper.xml │ │ │ ├── GroupMapper.xml │ │ │ └── PermissionMapper.xml │ │ ├── application-dev.yml │ │ ├── application.yml │ │ ├── banner.txt │ │ ├── ValidationMessages.properties │ │ └── code-message.properties │ └── java │ │ └── io │ │ └── github │ │ └── talelin │ │ └── latticy │ │ ├── module │ │ ├── message │ │ │ ├── MessageConstant.java │ │ │ ├── MessageWebSocketHandler.java │ │ │ ├── WebsocketConfiguration.java │ │ │ ├── WsHandler.java │ │ │ └── WebSocketInterceptor.java │ │ ├── file │ │ │ ├── UploadHandler.java │ │ │ ├── FileTypeEnum.java │ │ │ ├── Uploader.java │ │ │ ├── File.java │ │ │ ├── FileUtil.java │ │ │ └── FileProperties.java │ │ └── log │ │ │ └── MDCAccessConstant.java │ │ ├── bo │ │ ├── LoginCaptchaBO.java │ │ ├── FileBO.java │ │ └── GroupPermissionBO.java │ │ ├── mapper │ │ ├── UserIdentityMapper.java │ │ ├── FileMapper.java │ │ ├── BookMapper.java │ │ ├── UserGroupMapper.java │ │ ├── GroupPermissionMapper.java │ │ ├── UserMapper.java │ │ ├── PermissionMapper.java │ │ ├── GroupMapper.java │ │ └── LogMapper.java │ │ ├── common │ │ ├── constant │ │ │ └── IdentityConstant.java │ │ ├── factory │ │ │ └── YamlPropertySourceFactory.java │ │ ├── enumeration │ │ │ └── GroupLevelEnum.java │ │ ├── util │ │ │ ├── PageUtil.java │ │ │ ├── IPUtil.java │ │ │ └── ResponseUtil.java │ │ ├── LocalUser.java │ │ ├── configuration │ │ │ ├── CodeMessageConfiguration.java │ │ │ ├── CustomServletRequestDataBinder.java │ │ │ ├── CustomServletModelAttributeMethodProcessor.java │ │ │ ├── LoginCaptchaProperties.java │ │ │ ├── CommonConfiguration.java │ │ │ └── WebConfiguration.java │ │ ├── interceptor │ │ │ ├── RequestLogInterceptor.java │ │ │ └── LoggerImpl.java │ │ ├── aop │ │ │ └── ResultAspect.java │ │ ├── mybatis │ │ │ └── LinPage.java │ │ └── listener │ │ │ └── PermissionHandleListener.java │ │ ├── vo │ │ ├── LoginCaptchaVO.java │ │ ├── PageResponseVO.java │ │ ├── UserPermissionVO.java │ │ ├── DeletedVO.java │ │ ├── UpdatedVO.java │ │ ├── CreatedVO.java │ │ ├── UserInfoVO.java │ │ └── UnifyResponseVO.java │ │ ├── dto │ │ ├── admin │ │ │ ├── UpdateUserInfoDTO.java │ │ │ ├── QueryUsersDTO.java │ │ │ ├── RemovePermissionsDTO.java │ │ │ ├── UpdateGroupDTO.java │ │ │ ├── DispatchPermissionsDTO.java │ │ │ ├── DispatchPermissionDTO.java │ │ │ ├── NewGroupDTO.java │ │ │ └── ResetPasswordDTO.java │ │ ├── user │ │ │ ├── LoginDTO.java │ │ │ ├── UpdateInfoDTO.java │ │ │ ├── ChangePasswordDTO.java │ │ │ └── RegisterDTO.java │ │ ├── log │ │ │ └── QueryLogDTO.java │ │ ├── query │ │ │ └── BasePageDTO.java │ │ └── book │ │ │ └── CreateOrUpdateBookDTO.java │ │ ├── extension │ │ └── file │ │ │ ├── config.yml │ │ │ ├── UploaderConfiguration.java │ │ │ ├── LocalUploader.java │ │ │ └── QiniuUploader.java │ │ ├── model │ │ ├── BookDO.java │ │ ├── BaseModel.java │ │ ├── PermissionDO.java │ │ ├── FileDO.java │ │ ├── UserIdentityDO.java │ │ ├── UserGroupDO.java │ │ ├── LogDO.java │ │ ├── GroupPermissionDO.java │ │ ├── UserDO.java │ │ └── GroupDO.java │ │ ├── service │ │ ├── FileService.java │ │ ├── BookService.java │ │ ├── PermissionService.java │ │ ├── LogService.java │ │ ├── impl │ │ │ ├── BookServiceImpl.java │ │ │ ├── LogServiceImpl.java │ │ │ ├── FileServiceImpl.java │ │ │ ├── UserIdentityServiceImpl.java │ │ │ └── PermissionServiceImpl.java │ │ ├── UserIdentityService.java │ │ ├── GroupService.java │ │ ├── AdminService.java │ │ └── UserService.java │ │ ├── controller │ │ ├── cms │ │ │ ├── FileController.java │ │ │ └── LogController.java │ │ └── v1 │ │ │ └── BookController.java │ │ └── LatticyApplication.java └── test │ ├── java │ ├── io │ │ └── github │ │ │ └── talelin │ │ │ └── latticy │ │ │ ├── LatticyApplicationTests.java │ │ │ ├── mapper │ │ │ ├── FileMapperTest.java │ │ │ ├── BookMapperTest.java │ │ │ ├── UserMapperTest.java │ │ │ ├── LogMapperTest.java │ │ │ └── GroupMapperTest.java │ │ │ └── service │ │ │ └── impl │ │ │ ├── LogServiceImplTest.java │ │ │ ├── BookServiceImplTest.java │ │ │ └── UserIdentityServiceImplTest.java │ └── Jackson2Test.java │ └── resources │ └── mpg │ └── templates │ ├── mapper.xml.ftl │ └── controller.java.ftl ├── .gitignore ├── LICENSE └── README.md /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 提出问题 3 | about: 关于项目的疑问 4 | --- 5 | 6 | 请详细描述您对本项目的任何问题,我们会在第一时间查阅和解决。 7 | -------------------------------------------------------------------------------- /src/main/resources/DejaVuSerif-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TaleLin/lin-cms-spring-boot/HEAD/src/main/resources/DejaVuSerif-Bold.ttf -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/module/message/MessageConstant.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.module.message; 2 | 3 | /** 4 | * websocket 模块常量 5 | * 6 | * @author pedro@TaleLin 7 | */ 8 | public class MessageConstant { 9 | 10 | private MessageConstant() {} 11 | 12 | public static final String USER_KEY = "user"; 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | java-version: [ 8, 9, 11 ] 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-java@v1 14 | with: 15 | java-version: ${{ matrix.java-version }} 16 | - name: Build with Maven 17 | run: mvn test 18 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/bo/LoginCaptchaBO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.bo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * @author Gadfly 9 | * @since 2021-11-19 15:20 10 | * 登录验证码业务对象 11 | */ 12 | @Data 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class LoginCaptchaBO { 16 | private String captcha; 17 | private Long expired; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/mapper/UserIdentityMapper.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.mapper; 2 | 3 | import io.github.talelin.latticy.model.UserIdentityDO; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.springframework.stereotype.Repository; 6 | 7 | /** 8 | * @author pedro@TaleLin 9 | * 用户身份标识mapper接口 10 | */ 11 | @Repository 12 | public interface UserIdentityMapper extends BaseMapper { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/constant/IdentityConstant.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.constant; 2 | 3 | /** 4 | * 身份认证常量 5 | * 6 | * @author pedro@TaleLin 7 | */ 8 | public class IdentityConstant { 9 | 10 | /** 11 | * 表示通过用户名和密码来进行身份认证 12 | */ 13 | public static final String USERNAME_PASSWORD_IDENTITY = "USERNAME_PASSWORD"; 14 | 15 | private IdentityConstant() { 16 | throw new IllegalStateException("Utility class"); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/vo/LoginCaptchaVO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * @author Gadfly 9 | * 登录验证码视图对象 10 | */ 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class LoginCaptchaVO { 15 | /** 16 | * 加密后的验证码 17 | */ 18 | private String tag; 19 | /** 20 | * 验证码图片地址,可使用base64 21 | */ 22 | private String image; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/dto/admin/UpdateUserInfoDTO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.dto.admin; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.Min; 6 | import javax.validation.constraints.NotEmpty; 7 | import java.util.List; 8 | 9 | /** 10 | * @author pedro@TaleLin 11 | * @author Juzi@TaleLin 12 | * 用户信息更新数据传输对象 13 | */ 14 | @Data 15 | public class UpdateUserInfoDTO { 16 | 17 | @NotEmpty(message = "{group.ids.not-empty}") 18 | private List<@Min(1) Integer> groupIds; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/module/file/UploadHandler.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.module.file; 2 | 3 | /** 4 | * 上传预处理器 5 | * 6 | * @author pedro@TaleLin 7 | */ 8 | public interface UploadHandler { 9 | 10 | /** 11 | * 在文件写入本地或者上传到云之前调用此方法 12 | * 13 | * @param file 文件对象 14 | * @return 是否上传,若返回false,则不上传 15 | */ 16 | boolean preHandle(File file); 17 | 18 | /** 19 | * 在文件写入本地或者上传到云之后调用此方法 20 | * 21 | * @param file 文件对象 22 | */ 23 | void afterHandle(File file); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/bo/FileBO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.bo; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author pedro@TaleLin 7 | * @author Juzi@TaleLin 8 | */ 9 | @Data 10 | public class FileBO { 11 | 12 | /** 13 | * 文件 id 14 | */ 15 | private Integer id; 16 | 17 | /** 18 | * 文件 key,上传时指定的 19 | */ 20 | private String key; 21 | 22 | /** 23 | * 文件路径 24 | */ 25 | private String path; 26 | 27 | /** 28 | * 文件 URL 29 | */ 30 | private String url; 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | **/target/ 7 | config/ 8 | 9 | **.DS_Store 10 | 11 | ### STS ### 12 | .apt_generated 13 | .classpath 14 | .factorypath 15 | .project 16 | .settings 17 | .springBeans 18 | .sts4-cache 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | 26 | ### NetBeans ### 27 | /nbproject/private/ 28 | /nbbuild/ 29 | /dist/ 30 | /nbdist/ 31 | /.nb-gradle/ 32 | build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | assets/ 37 | logs/ 38 | 39 | 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 提出新特性 3 | about: 对项目的发展提出建议 4 | --- 5 | 6 | CMS 是一个颇为复杂的应用,它需要的东西太多。我们无法涉及到方方面面,因此关于新特性,我们会以讨论的形式来确定这个特性是否去实现,以什么形式实现。 7 | 我们鼓励所有对这个特性感兴趣的人来参与讨论,当然如果你想参与特性的开发那就更好了。 8 | 9 | 如果你实现了一个 feature,并通过了单元测试,请用`git rebase`合并成一条标准的`feat: description`提交,然后向我们的 10 | 项目提 PR,我们会在第一时间审核,并感谢您的参与。 11 | 12 | **请问这个特性跟什么问题相关? 有哪些应用场景?请详细描述。** 13 | 请清晰准确的描述问题的内容,以及真实的场景。 14 | 15 | **请描述一下你想怎么实现这个特性** 16 | 怎么样去实现这个特性?加入核心库?加入工程项目?还是其他方式。 17 | 当然你也可以描述它的具体实现. 18 | 19 | **讨论** 20 | 如果这个特性应用场景非常多,或者非常重要,我们会第一时间去处理。但更多的我们希望更多的人参与讨论,来斟酌它的可行性。 21 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/extension/file/config.yml: -------------------------------------------------------------------------------- 1 | #文件上传配置 2 | 3 | lin: 4 | file: 5 | # 文件服务域名 6 | domain: http://localhost:5000/ 7 | # 排除文件类型 8 | exclude: 9 | # 包括文件类型 10 | include: 11 | - .jpg 12 | - .png 13 | - .jpeg 14 | # 文件最大数量 15 | nums: 10 16 | # 服务器文件路径 17 | serve-path: assets/** 18 | # 单个文件最大体积 19 | single-limit: 2MB 20 | # 本地文件保存位置 21 | store-dir: assets/ 22 | spring: 23 | servlet: 24 | multipart: 25 | # 总体文件最大体积(只能从max-file-size设置总体文件的大小) 26 | max-file-size: 20MB 27 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | target-branch: "dev" 11 | schedule: 12 | interval: "daily" 13 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/dto/admin/QueryUsersDTO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.dto.admin; 2 | 3 | import io.github.talelin.latticy.dto.query.BasePageDTO; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | import javax.validation.constraints.Min; 8 | 9 | /** 10 | * @author Gadfly 11 | * @since 2021-06-28 18:48 12 | * 用户查询数据传输对象 13 | */ 14 | @Data 15 | @EqualsAndHashCode(callSuper = true) 16 | public class QueryUsersDTO extends BasePageDTO { 17 | 18 | @Min(value = 1, message = "{group.id.positive}") 19 | private Integer groupId; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/vo/PageResponseVO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.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 | /** 11 | * 分页数据统一视图对象 12 | * 13 | * @author pedro@TaleLin 14 | */ 15 | @Data 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | @Builder 19 | public class PageResponseVO { 20 | 21 | private Integer total; 22 | 23 | private List items; 24 | 25 | private Integer page; 26 | 27 | private Integer count; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- 1 | #生产环境配置 2 | 3 | server: 4 | # 服务端口 5 | port: 5000 6 | 7 | spring: 8 | # 数据源配置,请修改为你项目的实际配置 9 | datasource: 10 | username: "root" 11 | password: "123456" 12 | driver-class-name: com.mysql.cj.jdbc.Driver 13 | url: jdbc:mysql://localhost:3306/lin-cms?useSSL=false&serverTimezone=UTC&characterEncoding=UTF8 14 | 15 | # 开启权限拦截 16 | auth: 17 | enabled: true 18 | 19 | # 开启登录要求验证码 20 | login-captcha: 21 | enabled: false 22 | secret: "m49CPM5ak@MDXTzbbT_ZEyMM3KBsBn!h" 23 | 24 | # 开启请求日志记录 25 | request-log: 26 | enabled: true 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/dto/user/LoginDTO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.dto.user; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | 6 | import javax.validation.constraints.NotBlank; 7 | 8 | /** 9 | * @author pedro@TaleLin 10 | * @author Juzi@TaleLin 11 | * 登录数据传输对象 12 | */ 13 | @Data 14 | @NoArgsConstructor 15 | public class LoginDTO { 16 | 17 | @NotBlank(message = "{username.not-blank}") 18 | private String username; 19 | 20 | @NotBlank(message = "{password.new.not-blank}") 21 | private String password; 22 | 23 | private String captcha; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/dto/admin/RemovePermissionsDTO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.dto.admin; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.NotNull; 6 | import javax.validation.constraints.Positive; 7 | import java.util.List; 8 | 9 | /** 10 | * @author pedro@TaleLin 11 | * @author Juzi@TaleLin 12 | * 删除权限数据传输对象 13 | */ 14 | @Data 15 | public class RemovePermissionsDTO { 16 | 17 | @Positive(message = "{group.id.positive}") 18 | @NotNull(message = "{group.id.not-null}") 19 | private Integer groupId; 20 | 21 | private List permissionIds; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/dto/admin/UpdateGroupDTO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.dto.admin; 2 | 3 | import lombok.Data; 4 | import org.hibernate.validator.constraints.Length; 5 | 6 | import javax.validation.constraints.NotBlank; 7 | 8 | /** 9 | * @author pedro@TaleLin 10 | * 分组更新数据传输对象 11 | */ 12 | @Data 13 | public class UpdateGroupDTO { 14 | 15 | @NotBlank(message = "{group.name.not-blank}") 16 | @Length(min = 1, max = 60, message = "{group.name.length}") 17 | private String name; 18 | 19 | @Length(max = 255, message = "{group.info.length}") 20 | private String info; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/dto/admin/DispatchPermissionsDTO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.dto.admin; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.NotNull; 6 | import javax.validation.constraints.Positive; 7 | import java.util.List; 8 | 9 | /** 10 | * @author pedro@TaleLin 11 | * @author Juzi@TaleLin 12 | * 分配权限(多个)数据传输对象 13 | */ 14 | @Data 15 | public class DispatchPermissionsDTO { 16 | 17 | @Positive(message = "{group.id.positive}") 18 | @NotNull(message = "{group.id.not-null}") 19 | private Integer groupId; 20 | 21 | private List permissionIds; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | # 测试环境环境配置 2 | 3 | server: 4 | # 服务端口 5 | port: 5000 6 | 7 | logging: 8 | level: 9 | # SQL日志记录 10 | io.github.talelin.latticy.mapper: debug 11 | 12 | spring: 13 | # h2 内存数据库配置,供测试使用,其它环境勿用 14 | datasource: 15 | username: "sa" 16 | password: '' 17 | driver-class-name: org.h2.Driver 18 | url: jdbc:h2:mem:testdbsa;MODE=MYSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false 19 | h2: 20 | console: 21 | enabled: true 22 | path: /h2 23 | sql: 24 | init: 25 | continue-on-error: false 26 | schema-locations: classpath:/h2-test.sql 27 | platform: h2 28 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/module/file/FileTypeEnum.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.module.file; 2 | 3 | import com.baomidou.mybatisplus.annotation.IEnum; 4 | 5 | /** 6 | * @author colorful 7 | * 文件类型枚举 8 | */ 9 | public enum FileTypeEnum implements IEnum { 10 | 11 | /** 12 | * 本地文件 13 | */ 14 | LOCAL("LOCAL"), 15 | 16 | /** 17 | * 远程文件,例如OSS 18 | */ 19 | REMOTE("REMOTE") 20 | ; 21 | 22 | final String value; 23 | 24 | FileTypeEnum(String value) { 25 | this.value = value; 26 | } 27 | 28 | @Override 29 | public String getValue() { 30 | return value; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/io/github/talelin/latticy/LatticyApplicationTests.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy; 2 | 3 | import io.github.talelin.latticy.module.file.FileProperties; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.context.ActiveProfiles; 8 | 9 | @SpringBootTest 10 | @ActiveProfiles("test") 11 | public class LatticyApplicationTests { 12 | 13 | @Autowired 14 | private FileProperties fileProperties; 15 | 16 | @Test 17 | public void contextLoads() { 18 | System.out.println(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/dto/admin/DispatchPermissionDTO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.dto.admin; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.NotNull; 6 | import javax.validation.constraints.Positive; 7 | 8 | /** 9 | * @author pedro@TaleLin 10 | * @author Juzi@TaleLin 11 | * 分配权限(单个)数据传输对象 12 | */ 13 | @Data 14 | public class DispatchPermissionDTO { 15 | 16 | @Positive(message = "{group.id.positive}") 17 | @NotNull(message = "{group.id.not-null}") 18 | private Integer groupId; 19 | 20 | @Positive(message = "{permission.id.positive}") 21 | @NotNull(message = "{permission.id.not-null}") 22 | private Integer permissionId; 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 提出一个bug 3 | about: 提出bug帮助我们完善项目 4 | --- 5 | 6 | **描述 bug** 7 | 8 | - 你是如何操作的? 9 | - 发生了什么? 10 | - 你觉得应该出现什么? 11 | 12 | **你使用哪个版本出现该问题?** 13 | 14 | 如果使用`master`,请表明是 master 分支,否则给出具体的版本号 15 | 16 | **如何再现** 17 | 18 | If your bug is deterministic, can you give a minimal reproducing code? 19 | Some bugs are not deterministic. Can you describe with precision in which context it happened? 20 | If this is possible, can you share your code? 21 | 22 | 如果你确定存在这个 bug,你能提供我们一个最小的实现代码吗? 23 | 一些 bug 是不确定,只会在某些条件下触发,你能详细描述一下具体的情况和提供复现的步骤吗? 24 | 当然如果你提供在线的 repo,那就再好不过了。 25 | 26 | 如果你发现了 bug,并修复了它,请用`git rebase`合并成一条标准的`fix: description`提交,然后向我们的 27 | 项目提 PR,我们会在第一时间审核,并感谢您的参与。 28 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/dto/admin/NewGroupDTO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.dto.admin; 2 | 3 | import lombok.Data; 4 | import org.hibernate.validator.constraints.Length; 5 | 6 | import javax.validation.constraints.NotBlank; 7 | import java.util.List; 8 | 9 | /** 10 | * @author pedro@TaleLin 11 | * @author Juzi@TaleLin 12 | * 分组新增数据传输对象 13 | */ 14 | @Data 15 | public class NewGroupDTO { 16 | @NotBlank(message = "{group.name.not-blank}") 17 | @Length(min = 1, max = 60, message = "{group.name.length}") 18 | private String name; 19 | 20 | @Length(max = 255, message = "{group.info.length}") 21 | private String info; 22 | 23 | private List permissionIds; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/model/BookDO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableName; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * @author pedro@TaleLin 11 | * @author Juzi@TaleLin 12 | * 图书数据对象 13 | */ 14 | @Data 15 | @TableName("book") 16 | @EqualsAndHashCode(callSuper = true) 17 | public class BookDO extends BaseModel implements Serializable { 18 | 19 | private static final long serialVersionUID = 3531805912578317266L; 20 | 21 | private String title; 22 | 23 | private String author; 24 | 25 | private String summary; 26 | 27 | private String image; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/mapper/UserIdentityMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/dto/user/UpdateInfoDTO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.dto.user; 2 | 3 | import lombok.*; 4 | import org.hibernate.validator.constraints.Length; 5 | 6 | import javax.validation.constraints.Email; 7 | 8 | /** 9 | * @author pedro@TaleLin 10 | * 更新用户信息数据传输对象 11 | */ 12 | @NoArgsConstructor 13 | @Data 14 | public class UpdateInfoDTO { 15 | 16 | @Email(message = "{email}") 17 | private String email; 18 | 19 | @Length(min = 2, max = 10, message = "{nickname.length}") 20 | private String nickname; 21 | 22 | @Length(min = 2, max = 10, message = "{username.length}") 23 | private String username; 24 | 25 | @Length(max = 500, message = "{avatar.length}") 26 | private String avatar; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | #开发环境配置 2 | 3 | server: 4 | # 服务端口 5 | port: 5000 6 | 7 | 8 | spring: 9 | # 数据源配置,请修改为你项目的实际配置 10 | datasource: 11 | username: "root" 12 | password: "123456" 13 | driver-class-name: com.mysql.cj.jdbc.Driver 14 | url: jdbc:mysql://localhost:3306/lin-cms?useSSL=false&serverTimezone=UTC&characterEncoding=UTF8 15 | 16 | 17 | # 开启权限拦截 18 | auth: 19 | enabled: true 20 | 21 | # 开启登录要求验证码 22 | login-captcha: 23 | enabled: false 24 | secret: "m49CPM5ak@MDXTzbbT_ZEyMM3KBsBn!h" 25 | 26 | # 开启http请求日志记录 27 | request-log: 28 | enabled: true 29 | 30 | 31 | logging: 32 | level: 33 | # web信息日志记录 34 | web: debug 35 | # SQL日志记录 36 | io.github.talelin.latticy.mapper: debug 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/module/file/Uploader.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.module.file; 2 | 3 | import org.springframework.util.MultiValueMap; 4 | import org.springframework.web.multipart.MultipartFile; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * 文件上传服务接口 10 | * 11 | * @author pedro@TaleLin 12 | */ 13 | public interface Uploader { 14 | 15 | /** 16 | * 上传文件 17 | * 18 | * @param fileMap 文件map 19 | * @return 文件数据 20 | */ 21 | List upload(MultiValueMap fileMap); 22 | 23 | /** 24 | * 上传文件 25 | * 26 | * @param fileMap 文件map 27 | * @param uploadHandler 预处理器 28 | * @return 文件数据 29 | */ 30 | List upload(MultiValueMap fileMap, UploadHandler uploadHandler); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/dto/admin/ResetPasswordDTO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.dto.admin; 2 | 3 | import io.github.talelin.autoconfigure.validator.EqualField; 4 | import lombok.Data; 5 | 6 | import javax.validation.constraints.NotBlank; 7 | import javax.validation.constraints.Pattern; 8 | 9 | /** 10 | * @author pedro@TaleLin 11 | * 重置密码数据传输对象 12 | */ 13 | @EqualField(srcField = "newPassword", dstField = "confirmPassword", message = "{password.equal-field}") 14 | @Data 15 | public class ResetPasswordDTO { 16 | 17 | @NotBlank(message = "{password.new.not-blank}") 18 | @Pattern(regexp = "^[A-Za-z0-9_*&$#@]{6,22}$", message = "{password.new.pattern}") 19 | private String newPassword; 20 | 21 | @NotBlank(message = "{password.confirm.not-blank}") 22 | private String confirmPassword; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/mapper/FileMapper.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.mapper; 2 | 3 | import io.github.talelin.latticy.model.FileDO; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Param; 6 | import org.springframework.stereotype.Repository; 7 | 8 | /** 9 | * @author pedro@TaleLin 10 | * 文件mapper接口 11 | */ 12 | @Repository 13 | public interface FileMapper extends BaseMapper { 14 | 15 | /** 16 | * 根据文件md5查询文件对象 17 | * @param md5 文件md5 18 | * @return 文件数据对象 19 | */ 20 | FileDO selectByMd5(@Param("md5") String md5); 21 | 22 | /** 23 | * 根据文件md5查询文件数量 24 | * @param md5 文件md5 25 | * @return 文件数据传输对象 26 | */ 27 | int selectCountByMd5(@Param("md5") String md5); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/dto/log/QueryLogDTO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.dto.log; 2 | 3 | import io.github.talelin.latticy.dto.query.BasePageDTO; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import org.springframework.format.annotation.DateTimeFormat; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * @author colorful@TaleLin 12 | * 日志查询数据传输对象 13 | */ 14 | @Data 15 | @EqualsAndHashCode(callSuper = true) 16 | public class QueryLogDTO extends BasePageDTO { 17 | 18 | protected static Integer defaultCount = 12; 19 | 20 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 21 | private Date start; 22 | 23 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 24 | private Date end; 25 | 26 | private String name; 27 | 28 | private String keyword; 29 | 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/model/BaseModel.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.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.fasterxml.jackson.annotation.JsonIgnore; 7 | import lombok.Data; 8 | 9 | import java.util.Date; 10 | 11 | /** 12 | * @author Juzi@TaleLin 13 | * 基础数据模型类 14 | */ 15 | @Data 16 | public class BaseModel { 17 | @TableId(value = "id", type = IdType.AUTO) 18 | private Integer id; 19 | 20 | @JsonIgnore 21 | private Date createTime; 22 | 23 | @JsonIgnore 24 | private Date updateTime; 25 | 26 | @JsonIgnore 27 | private Date deleteTime; 28 | 29 | @TableLogic 30 | @JsonIgnore 31 | private Boolean isDeleted; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/extension/file/UploaderConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.extension.file; 2 | 3 | import io.github.talelin.latticy.module.file.Uploader; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.core.annotation.Order; 8 | 9 | /** 10 | * 文件上传配置类 11 | * 12 | * @author Juzi@TaleLin 13 | * @author colorful@TaleLin 14 | */ 15 | @Configuration(proxyBeanMethods = false) 16 | public class UploaderConfiguration { 17 | /** 18 | * @return 本地文件上传实现类 19 | */ 20 | @Bean 21 | @Order 22 | @ConditionalOnMissingBean 23 | public Uploader uploader(){ 24 | return new LocalUploader(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/mapper/BookMapper.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import io.github.talelin.latticy.model.BookDO; 5 | import org.apache.ibatis.annotations.Param; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @author pedro@TaleLin 12 | * 图书mapper接口 13 | */ 14 | @Repository 15 | public interface BookMapper extends BaseMapper { 16 | 17 | /** 18 | * 根据标题模糊查询图书列表 19 | * @param q 查询关键字 20 | * @return 图书数据对象列表 21 | */ 22 | List selectByTitleLikeKeyword(@Param("q") String q); 23 | 24 | /** 25 | * 根据标题查询图书列表 26 | * @param title 图书标题 27 | * @return 图书数据对象列表 28 | */ 29 | List selectByTitle(@Param("title") String title); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/dto/query/BasePageDTO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.dto.query; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.Max; 6 | import javax.validation.constraints.Min; 7 | 8 | /** 9 | * @author Gadfly 10 | * 基础分页数据传输对象 11 | */ 12 | @Data 13 | public class BasePageDTO { 14 | 15 | @Min(value = 1, message = "{page.count.min}") 16 | @Max(value = 30, message = "{page.count.max}") 17 | private Integer count; 18 | 19 | @Min(value = 0, message = "{page.number.min}") 20 | private Integer page; 21 | 22 | public Integer getCount() { 23 | if (null == count) { 24 | return 10; 25 | } 26 | return count; 27 | } 28 | 29 | public Integer getPage() { 30 | if (null == page) { 31 | return 0; 32 | } 33 | return page; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/mapper/UserGroupMapper.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.mapper; 2 | 3 | import io.github.talelin.latticy.model.UserGroupDO; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Param; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @author pedro@TaleLin 12 | * 用户分组mapper 13 | */ 14 | @Repository 15 | public interface UserGroupMapper extends BaseMapper { 16 | 17 | /** 18 | * 批量新增 19 | * @param relations 用户分组数据对象集合 20 | * @return last insert id 21 | */ 22 | int insertBatch(@Param("relations") List relations); 23 | 24 | /** 25 | * 根据用户id删除 26 | * @param userId 用户id 27 | * @return 受影响行数 28 | */ 29 | int deleteByUserId(@Param("user_id") Integer userId); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/factory/YamlPropertySourceFactory.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.factory; 2 | 3 | import org.springframework.boot.env.YamlPropertySourceLoader; 4 | import org.springframework.core.env.PropertySource; 5 | import org.springframework.core.io.support.EncodedResource; 6 | import org.springframework.core.io.support.PropertySourceFactory; 7 | 8 | import java.io.IOException; 9 | import java.util.List; 10 | 11 | /** 12 | * @author Juzi@TaleLin 13 | * YAML配置预加载 14 | */ 15 | public class YamlPropertySourceFactory implements PropertySourceFactory { 16 | @Override 17 | public PropertySource createPropertySource(String name, EncodedResource resource) throws IOException { 18 | List> sources = new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()); 19 | return sources.get(0); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/model/PermissionDO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableName; 4 | import lombok.*; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * @author pedro@TaleLin 10 | * @author Juzi@TaleLin 11 | * 权限数据对象 12 | */ 13 | @Data 14 | @Builder 15 | @TableName("lin_permission") 16 | @EqualsAndHashCode(callSuper = true) 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | public class PermissionDO extends BaseModel implements Serializable { 20 | 21 | private static final long serialVersionUID = -2400022443732120128L; 22 | 23 | /** 24 | * 权限名称,例如:访问首页 25 | */ 26 | private String name; 27 | 28 | /** 29 | * 权限所属模块,例如:人员管理 30 | */ 31 | private String module; 32 | 33 | /** 34 | * 0:关闭 1:开启 35 | */ 36 | private Boolean mount; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/model/FileDO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableName; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * @author pedro@TaleLin 11 | * @author Juzi@TaleLin 12 | * 文件数据对象 13 | */ 14 | @Data 15 | @TableName("lin_file") 16 | @EqualsAndHashCode(callSuper = true) 17 | public class FileDO extends BaseModel implements Serializable { 18 | 19 | private static final long serialVersionUID = -3203293656352763490L; 20 | 21 | private String path; 22 | 23 | /** 24 | * LOCAL REMOTE 25 | */ 26 | private String type; 27 | 28 | private String name; 29 | 30 | private String extension; 31 | 32 | private Integer size; 33 | 34 | /** 35 | * md5值,防止上传重复文件 36 | */ 37 | private String md5; 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/service/FileService.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.service; 2 | 3 | import io.github.talelin.latticy.bo.FileBO; 4 | import io.github.talelin.latticy.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@TaleLin 13 | * 文件服务接口 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 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/bo/GroupPermissionBO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.bo; 2 | 3 | import io.github.talelin.latticy.model.GroupDO; 4 | import io.github.talelin.latticy.model.PermissionDO; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.springframework.beans.BeanUtils; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * @author pedro@TaleLin 14 | * @author Juzi@TaleLin 15 | * @author colorful@TaleLin 16 | * 分组权限业务对象 17 | */ 18 | @Data 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | public class GroupPermissionBO { 22 | private Integer id; 23 | 24 | private String name; 25 | 26 | private String info; 27 | 28 | private List permissions; 29 | 30 | public GroupPermissionBO(GroupDO group, List permissions) { 31 | BeanUtils.copyProperties(group, this); 32 | this.permissions = permissions; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/module/file/File.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.module.file; 2 | 3 | import lombok.*; 4 | 5 | /** 6 | * @author pedro@TaleLin 7 | * 文件对象 8 | */ 9 | @Setter 10 | @Getter 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Builder 14 | public class File { 15 | 16 | /** 17 | * 真实url 18 | */ 19 | private String url; 20 | 21 | /** 22 | * 前端上传的key 23 | */ 24 | private String key; 25 | 26 | /** 27 | * 若 local,表示文件路径 28 | */ 29 | private String path; 30 | 31 | /** 32 | * LOCAL REMOTE 33 | */ 34 | private String type; 35 | 36 | /** 37 | * 文件名称 38 | */ 39 | private String name; 40 | 41 | /** 42 | * 扩展名,例:.jpg 43 | */ 44 | private String extension; 45 | 46 | /** 47 | * 文件大小 48 | */ 49 | private Integer size; 50 | 51 | /** 52 | * md5值,防止上传重复文件 53 | */ 54 | private String md5; 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/enumeration/GroupLevelEnum.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.enumeration; 2 | 3 | 4 | import com.baomidou.mybatisplus.annotation.IEnum; 5 | 6 | /** 7 | * @author colorful@TaleLin 8 | * @author Juzi@TaleLin 9 | * 10 | * 分组级别枚举类 11 | */ 12 | public enum GroupLevelEnum implements IEnum { 13 | /** 14 | * 超级管理员 15 | */ 16 | ROOT(1), 17 | /** 18 | * 游客 19 | */ 20 | GUEST(2), 21 | /** 22 | * 普通用户 23 | */ 24 | USER(3); 25 | 26 | private final Integer value; 27 | 28 | GroupLevelEnum(Integer value) { 29 | this.value = value; 30 | } 31 | 32 | /** 33 | * MybatisEnumTypeHandler 转换时调用此方法 34 | * 35 | * @return 枚举对应的 code 值 36 | * @see com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler 37 | */ 38 | @Override 39 | public Integer getValue() { 40 | return this.value; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/model/UserIdentityDO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableName; 4 | import lombok.*; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * @author pedro@TaleLin 10 | * @author Juzi@TaleLin 11 | * 用户鉴权标识数据对象 12 | */ 13 | @Data 14 | @Builder 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @TableName("lin_user_identity") 18 | public class UserIdentityDO extends BaseModel implements Serializable { 19 | 20 | private static final long serialVersionUID = 456555840105356178L; 21 | 22 | /** 23 | * 用户id 24 | */ 25 | private Integer userId; 26 | 27 | /** 28 | * 认证类型,例如 username_password,用户名-密码认证 29 | */ 30 | private String identityType; 31 | 32 | /** 33 | * 认证,例如 用户名 34 | */ 35 | private String identifier; 36 | 37 | /** 38 | * 凭证,例如 密码 39 | */ 40 | private String credential; 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/dto/book/CreateOrUpdateBookDTO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.dto.book; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import org.hibernate.validator.constraints.Length; 6 | 7 | import javax.validation.constraints.NotEmpty; 8 | 9 | /** 10 | * @author pedro@TaleLin 11 | * @author Juzi@TaleLin 12 | * 图书创建/更新数据传输对象 13 | */ 14 | @Data 15 | @NoArgsConstructor 16 | public class CreateOrUpdateBookDTO { 17 | 18 | @NotEmpty(message = "{book.title.not-empty}") 19 | @Length(max = 50, message = "{book.title.length}") 20 | private String title; 21 | 22 | @NotEmpty(message = "{book.author.not-empty}") 23 | @Length(max = 50, message = "{book.author.length}") 24 | private String author; 25 | 26 | @NotEmpty(message = "{book.summary.not-empty}") 27 | @Length(max = 1000, message = "{book.summary.length}") 28 | private String summary; 29 | 30 | @Length(max = 100, message = "{book.image.length}") 31 | private String image; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/dto/user/ChangePasswordDTO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.dto.user; 2 | 3 | import io.github.talelin.autoconfigure.validator.EqualField; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.NotBlank; 8 | import javax.validation.constraints.Pattern; 9 | 10 | /** 11 | * @author pedro@TaleLin 12 | * @author Juzi@TaleLin 13 | * 密码修改数据传输对象 14 | */ 15 | @Data 16 | @NoArgsConstructor 17 | @EqualField(srcField = "newPassword", dstField = "confirmPassword", message = "{password.equal-field}") 18 | public class ChangePasswordDTO { 19 | 20 | @NotBlank(message = "{password.new.not-blank}") 21 | @Pattern(regexp = "^[A-Za-z0-9_*&$#@]{6,22}$", message = "{password.new.pattern}") 22 | private String newPassword; 23 | 24 | @NotBlank(message = "{password.confirm.not-blank}") 25 | private String confirmPassword; 26 | 27 | @NotBlank(message = "{password.old.not-blank}") 28 | private String oldPassword; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/util/PageUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.util; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import io.github.talelin.latticy.vo.PageResponseVO; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author colorful@TaleLin 10 | * 分页工具类 11 | */ 12 | public class PageUtil { 13 | 14 | private PageUtil() { 15 | throw new IllegalStateException("Utility class"); 16 | } 17 | 18 | public static PageResponseVO build(IPage iPage) { 19 | return new PageResponseVO<>(Math.toIntExact(iPage.getTotal()), iPage.getRecords(), 20 | Math.toIntExact(iPage.getCurrent()), Math.toIntExact(iPage.getSize())); 21 | } 22 | 23 | public static PageResponseVO build(IPage iPage, List records) { 24 | return new PageResponseVO<>(Math.toIntExact(iPage.getTotal()), records, 25 | Math.toIntExact(iPage.getCurrent()), 26 | Math.toIntExact(iPage.getSize())); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /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 | DELETE FROM lin_user_group 22 | WHERE user_id = #{user_id} 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/model/UserGroupDO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.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@TaleLin 12 | * @author Juzi@TaleLin 13 | * 用户分组数据对象 14 | */ 15 | @Data 16 | @TableName("lin_user_group") 17 | public class UserGroupDO implements Serializable { 18 | 19 | private static final long serialVersionUID = -7219009955825484511L; 20 | 21 | @TableId(value = "id", type = IdType.AUTO) 22 | private Integer id; 23 | 24 | /** 25 | * 用户id 26 | */ 27 | private Integer userId; 28 | 29 | /** 30 | * 分组id 31 | */ 32 | private Integer groupId; 33 | 34 | public UserGroupDO(Integer userId, Integer groupId) { 35 | this.userId = userId; 36 | this.groupId = groupId; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/model/LogDO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableName; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.io.Serializable; 11 | 12 | /** 13 | * @author pedro@TaleLin 14 | * @author Juzi@TaleLin 15 | * 日志数据对象 16 | */ 17 | @Data 18 | @Builder 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | @TableName("lin_log") 22 | @EqualsAndHashCode(callSuper = true) 23 | public class LogDO extends BaseModel implements Serializable { 24 | 25 | private static final long serialVersionUID = -7471474245124682011L; 26 | 27 | private String message; 28 | 29 | private Integer userId; 30 | 31 | private String username; 32 | 33 | private Integer statusCode; 34 | 35 | private String method; 36 | 37 | private String path; 38 | 39 | private String permission; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/model/GroupPermissionDO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.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@TaleLin 12 | * @author Juzi@TaleLin 13 | * 分组权限数据对象 14 | */ 15 | @Data 16 | @TableName("lin_group_permission") 17 | public class GroupPermissionDO implements Serializable { 18 | 19 | private static final long serialVersionUID = -358487811336536495L; 20 | 21 | @TableId(value = "id", type = IdType.AUTO) 22 | private Integer id; 23 | 24 | /** 25 | * 分组id 26 | */ 27 | private Integer groupId; 28 | 29 | /** 30 | * 权限id 31 | */ 32 | private Integer permissionId; 33 | 34 | public GroupPermissionDO(Integer groupId, Integer permissionId) { 35 | this.groupId = groupId; 36 | this.permissionId = permissionId; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/mapper/GroupPermissionMapper.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import io.github.talelin.latticy.model.GroupPermissionDO; 5 | import org.apache.ibatis.annotations.Param; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @author pedro@TaleLin 12 | * @author Juzi@TaleLin 13 | * 分组权限mapper接口 14 | */ 15 | @Repository 16 | public interface GroupPermissionMapper extends BaseMapper { 17 | 18 | /** 19 | * 批量新增 20 | * @param relations 分组权限关系集合 21 | * @return last insert id 22 | */ 23 | int insertBatch(@Param("relations") List relations); 24 | 25 | /** 26 | * 根据分组id和权限id集合批量删除分组权限关系 27 | * @param groupId 分组id 28 | * @param permissionIds 权限id集合 29 | * @return 受影响行数 30 | */ 31 | int deleteBatchByGroupIdAndPermissionId(@Param("groupId") Integer groupId, @Param("permissionIds") List permissionIds); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/vo/UserPermissionVO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.vo; 2 | 3 | import io.github.talelin.latticy.model.UserDO; 4 | import lombok.Data; 5 | import org.springframework.beans.BeanUtils; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * 用户 + 权限 view object 12 | * 13 | * @author pedro@TaleLin 14 | * @author Juzi@TaleLin 15 | */ 16 | @Data 17 | public class UserPermissionVO { 18 | 19 | private Integer id; 20 | 21 | private String nickname; 22 | 23 | private String avatar; 24 | 25 | private Boolean admin; 26 | 27 | private String email; 28 | 29 | private List>>> permissions; 30 | 31 | public UserPermissionVO() { 32 | } 33 | 34 | public UserPermissionVO(UserDO userDO, List>>> permissions) { 35 | BeanUtils.copyProperties(userDO, this); 36 | this.permissions = permissions; 37 | } 38 | 39 | public UserPermissionVO(UserDO userDO) { 40 | BeanUtils.copyProperties(userDO, this); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/LocalUser.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common; 2 | 3 | import io.github.talelin.latticy.model.UserDO; 4 | 5 | /** 6 | * 线程安全的当前登录用户,如果用户未登录,则得到 null 7 | * 8 | * @author pedro@TaleLin 9 | */ 10 | public class LocalUser { 11 | 12 | private LocalUser() { 13 | throw new IllegalStateException("Utility class"); 14 | } 15 | 16 | private static final ThreadLocal LOCAL = new ThreadLocal<>(); 17 | 18 | /** 19 | * 得到当前登录用户 20 | * 21 | * @return user | null 22 | */ 23 | public static UserDO getLocalUser() { 24 | return LocalUser.LOCAL.get(); 25 | } 26 | 27 | /** 28 | * 设置登录用户 29 | * 30 | * @param user user 31 | */ 32 | public static void setLocalUser(UserDO user) { 33 | LocalUser.LOCAL.set(user); 34 | } 35 | 36 | public static T getLocalUser(Class clazz) { 37 | return (T) LOCAL.get(); 38 | } 39 | 40 | /** 41 | * 清理当前用户 42 | */ 43 | public static void clearLocalUser() { 44 | LOCAL.remove(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/model/UserDO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableName; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.io.Serializable; 11 | 12 | /** 13 | * @author pedro@TaleLin 14 | * @author Juzi@TaleLin 15 | * 用户数据对象 16 | */ 17 | @Data 18 | @Builder 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | @TableName("lin_user") 22 | @EqualsAndHashCode(callSuper = true) 23 | public class UserDO extends BaseModel implements Serializable { 24 | 25 | private static final long serialVersionUID = -1463999384554707735L; 26 | 27 | /** 28 | * 用户名,唯一 29 | */ 30 | private String username; 31 | 32 | /** 33 | * 用户昵称 34 | */ 35 | private String nickname; 36 | 37 | /** 38 | * 头像url 39 | */ 40 | private String avatar; 41 | 42 | /** 43 | * 邮箱 44 | */ 45 | private String email; 46 | 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 lin 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 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/configuration/CodeMessageConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.configuration; 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 | /** 11 | * @author pedro@TaleLin 12 | * 13 | * 消息码配置类 14 | */ 15 | @SuppressWarnings("ConfigurationProperties") 16 | @Component 17 | @ConfigurationProperties 18 | @PropertySource(value = "classpath:code-message.properties", encoding = "UTF-8") 19 | public class CodeMessageConfiguration { 20 | 21 | private static Map codeMessage = new HashMap<>(); 22 | 23 | public static String getMessage(Integer code) { 24 | return codeMessage.get(code); 25 | } 26 | 27 | public Map getCodeMessage() { 28 | return codeMessage; 29 | } 30 | 31 | public void setCodeMessage(Map codeMessage) { 32 | CodeMessageConfiguration.codeMessage = codeMessage; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/model/GroupDO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableField; 4 | import com.baomidou.mybatisplus.annotation.TableName; 5 | import io.github.talelin.latticy.common.enumeration.GroupLevelEnum; 6 | import lombok.*; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * @author pedro@TaleLin 12 | * @author Juzi@TaleLin 13 | * @author Jokky@TaleLin 14 | * 分组数据对象 15 | */ 16 | @Data 17 | @Builder 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | @TableName(value = "lin_group") 21 | @EqualsAndHashCode(callSuper = true) 22 | public class GroupDO extends BaseModel implements Serializable { 23 | 24 | private static final long serialVersionUID = -8994898895671436007L; 25 | 26 | /** 27 | * 分组名称,例如:搬砖者 28 | */ 29 | private String name; 30 | 31 | /** 32 | * 分组信息:例如:搬砖的人 33 | */ 34 | private String info; 35 | 36 | /** 37 | * 分组级别(root、guest、user,其中 root、guest 分组只能存在一个) 38 | */ 39 | @TableField(value = "`level`") 40 | private GroupLevelEnum level; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/vo/DeletedVO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.vo; 2 | 3 | import io.github.talelin.autoconfigure.bean.Code; 4 | import io.github.talelin.latticy.common.util.ResponseUtil; 5 | import org.springframework.http.HttpStatus; 6 | 7 | /** 8 | * @author colorful@TaleLin 9 | * 删除成功视图对象 10 | */ 11 | public class DeletedVO extends UnifyResponseVO { 12 | 13 | public DeletedVO() { 14 | super(Code.DELETED.getCode()); 15 | ResponseUtil.setCurrentResponseHttpStatus(HttpStatus.OK.value()); 16 | } 17 | 18 | public DeletedVO(int code) { 19 | super(code); 20 | ResponseUtil.setCurrentResponseHttpStatus(HttpStatus.OK.value()); 21 | } 22 | 23 | public DeletedVO(String message) { 24 | super(message); 25 | ResponseUtil.setCurrentResponseHttpStatus(HttpStatus.OK.value()); 26 | } 27 | 28 | public DeletedVO(int code, String message) { 29 | super(code, message); 30 | ResponseUtil.setCurrentResponseHttpStatus(HttpStatus.OK.value()); 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return super.toString(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/vo/UpdatedVO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.vo; 2 | 3 | import io.github.talelin.autoconfigure.bean.Code; 4 | import io.github.talelin.latticy.common.util.ResponseUtil; 5 | import org.springframework.http.HttpStatus; 6 | 7 | /** 8 | * @author pedro@TaleLin 9 | * 更新成功视图对象 10 | */ 11 | public class UpdatedVO extends UnifyResponseVO { 12 | 13 | public UpdatedVO() { 14 | super(Code.UPDATED.getCode()); 15 | ResponseUtil.setCurrentResponseHttpStatus(HttpStatus.OK.value()); 16 | } 17 | 18 | public UpdatedVO(int code) { 19 | super(code); 20 | ResponseUtil.setCurrentResponseHttpStatus(HttpStatus.OK.value()); 21 | } 22 | 23 | public UpdatedVO(String message) { 24 | super(message); 25 | ResponseUtil.setCurrentResponseHttpStatus(HttpStatus.OK.value()); 26 | } 27 | 28 | public UpdatedVO(int code, String message) { 29 | super(code, message); 30 | ResponseUtil.setCurrentResponseHttpStatus(HttpStatus.OK.value()); 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return super.toString(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/vo/CreatedVO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.vo; 2 | 3 | import io.github.talelin.autoconfigure.bean.Code; 4 | import io.github.talelin.latticy.common.util.ResponseUtil; 5 | import org.springframework.http.HttpStatus; 6 | 7 | /** 8 | * @author colorful@TaleLin 9 | * 创建成功视图对象 10 | */ 11 | public class CreatedVO extends UnifyResponseVO { 12 | 13 | public CreatedVO() { 14 | super(Code.CREATED.getCode()); 15 | ResponseUtil.setCurrentResponseHttpStatus(HttpStatus.CREATED.value()); 16 | } 17 | 18 | public CreatedVO(int code) { 19 | super(code); 20 | ResponseUtil.setCurrentResponseHttpStatus(HttpStatus.CREATED.value()); 21 | } 22 | 23 | public CreatedVO(String message) { 24 | super(message); 25 | ResponseUtil.setCurrentResponseHttpStatus(HttpStatus.CREATED.value()); 26 | } 27 | 28 | public CreatedVO(int code, String message) { 29 | super(code, message); 30 | ResponseUtil.setCurrentResponseHttpStatus(HttpStatus.CREATED.value()); 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return super.toString(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/vo/UserInfoVO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.vo; 2 | 3 | import io.github.talelin.latticy.model.GroupDO; 4 | import io.github.talelin.latticy.model.UserDO; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import org.springframework.beans.BeanUtils; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * 用户信息 view object 15 | * 16 | * @author pedro@TaleLin 17 | * @author colorful@TaleLin 18 | */ 19 | @Data 20 | @Builder 21 | @AllArgsConstructor 22 | @NoArgsConstructor 23 | public class UserInfoVO { 24 | 25 | private Integer id; 26 | 27 | /** 28 | * 用户名,唯一 29 | */ 30 | private String username; 31 | 32 | /** 33 | * 用户昵称 34 | */ 35 | private String nickname; 36 | 37 | /** 38 | * 头像url 39 | */ 40 | private String avatar; 41 | 42 | /** 43 | * 邮箱 44 | */ 45 | private String email; 46 | 47 | /** 48 | * 分组 49 | */ 50 | private List groups; 51 | 52 | public UserInfoVO(UserDO user, List groups) { 53 | BeanUtils.copyProperties(user, this); 54 | this.groups = groups; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | #主配置文件 2 | 3 | spring: 4 | # 激活环境配置 5 | profiles: 6 | active: dev 7 | # 文件编码 UTF8 8 | mandatory-file-encoding: UTF-8 9 | mvc: 10 | # 404 交给异常处理器处理 11 | throw-exception-if-no-handler-found: true 12 | web: 13 | resources: 14 | # 关闭默认静态资源的映射规则 15 | add-mappings: false 16 | 17 | mybatis-plus: 18 | configuration: 19 | # 开启下划线转驼峰 20 | map-underscore-to-camel-case: true 21 | # 指定默认枚举类型的类型转换器 22 | default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler 23 | global-config: 24 | # 开启/关闭 banner 打印 25 | banner: false 26 | db-config: 27 | # 逻辑删除(软删除) 28 | logic-delete-value: 1 29 | logic-not-delete-value: 0 30 | # mapper路径位置 31 | mapper-locations: classpath:mapper/*.xml 32 | 33 | 34 | lin: 35 | cms: 36 | # 开启行为日志记录(logger) 37 | logger-enabled: true 38 | # access token 过期时间,3600s 一个小时 39 | token-access-expire: 3600 40 | # refresh token 过期时间,2592000s 一个月 41 | token-refresh-expire: 2592000 42 | # 令牌 secret 43 | token-secret: x88Wf0991079889x8796a0Ac68f9ecJJU17c5Vbe8beod7d8d3e695*4 44 | logging: 45 | logback: 46 | rollingpolicy: 47 | max-history: 48 | config: 49 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.baomidou.mybatisplus.core.metadata.IPage; 5 | import io.github.talelin.latticy.common.mybatis.LinPage; 6 | import io.github.talelin.latticy.model.UserDO; 7 | import org.springframework.stereotype.Repository; 8 | 9 | 10 | /** 11 | * @author pedro@TaleLin 12 | * @author colorful@TaleLin 13 | * @author Juzi@TaleLin 14 | * 用户mapper接口 15 | */ 16 | @Repository 17 | public interface UserMapper extends BaseMapper { 18 | 19 | /** 20 | * 查询用户名为$username的人数 21 | * 22 | * @param username 用户名 23 | * @return 人数 24 | */ 25 | int selectCountByUsername(String username); 26 | 27 | /** 28 | * 查询用户id为$id的人数 29 | * 30 | * @param id 用户id 31 | * @return 人数 32 | */ 33 | int selectCountById(Integer id); 34 | 35 | /** 36 | * 通过分组id分页获取用户数据 37 | * 38 | * @param pager 分页 39 | * @param groupId 分组id 40 | * @param rootGroupId 超级用户组id(不返回超级用户组的用户) 41 | * @return 分页数据 42 | */ 43 | IPage selectPageByGroupId(LinPage pager, Integer groupId, Integer rootGroupId); 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/dto/user/RegisterDTO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.dto.user; 2 | 3 | import io.github.talelin.autoconfigure.validator.EqualField; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.hibernate.validator.constraints.Length; 7 | 8 | import javax.validation.constraints.Email; 9 | import javax.validation.constraints.NotBlank; 10 | import javax.validation.constraints.Pattern; 11 | import java.util.List; 12 | 13 | /** 14 | * @author pedro@TaleLin 15 | * @author Juzi@TaleLin 16 | * 注册数据传输对象 17 | */ 18 | @Data 19 | @NoArgsConstructor 20 | @EqualField(srcField = "password", dstField = "confirmPassword", message = "{password.equal-field}") 21 | public class RegisterDTO { 22 | 23 | @NotBlank(message = "{username.not-blank}") 24 | @Length(min = 2, max = 10, message = "{username.length}") 25 | private String username; 26 | 27 | private List groupIds; 28 | 29 | @Email(message = "{email}") 30 | private String email; 31 | 32 | @NotBlank(message = "{password.new.not-blank}") 33 | @Pattern(regexp = "^[A-Za-z0-9_*&$#@]{6,22}$", message = "{password.new.pattern}") 34 | private String password; 35 | 36 | @NotBlank(message = "{password.confirm.not-blank}") 37 | private String confirmPassword; 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/service/BookService.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.service; 2 | 3 | import io.github.talelin.latticy.dto.book.CreateOrUpdateBookDTO; 4 | import io.github.talelin.latticy.model.BookDO; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author pedro@TaleLin 10 | * @author Juzi@TaleLin 11 | * 图书服务接口 12 | */ 13 | public interface BookService { 14 | 15 | /** 16 | * 新增图书 17 | * @param validator 数据传输对象 18 | * @return 是否成功 19 | */ 20 | boolean createBook(CreateOrUpdateBookDTO validator); 21 | 22 | /** 23 | * 根据关键字获取图书集合 24 | * @param q 查询关键字 25 | * @return BookDO List 26 | */ 27 | List getBookByKeyword(String q); 28 | 29 | /** 30 | * 更新图书 31 | * @param book 图书对象 32 | * @param validator 数据传输对象 33 | * @return 是否更新成功 34 | */ 35 | boolean updateBook(BookDO book, CreateOrUpdateBookDTO validator); 36 | 37 | /** 38 | * 获取图书 39 | * @param id 主键id 40 | * @return 图书数据对象 41 | */ 42 | BookDO getById(Integer id); 43 | 44 | /** 45 | * 查询所有图书 46 | * @return 图书数据对象集合 47 | */ 48 | List findAll(); 49 | 50 | /** 51 | * 删除图书 52 | * @param id 主键id 53 | * @return 是否删除成功 54 | */ 55 | boolean deleteById(Integer id); 56 | } 57 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/mapper/PermissionMapper.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import io.github.talelin.latticy.model.PermissionDO; 5 | import org.apache.ibatis.annotations.Param; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @author pedro@TaleLin 12 | * @author Juzi@TaleLin 13 | * 权限mapper接口 14 | */ 15 | @Repository 16 | public interface PermissionMapper extends BaseMapper { 17 | 18 | /** 19 | * 通过分组ids得到所有分组下的权限 20 | * 21 | * @param groupIds 分组ids 22 | * @return 权限 23 | */ 24 | List selectPermissionsByGroupIds(@Param("groupIds") List groupIds); 25 | 26 | /** 27 | * 通过分组id得到所有分组下的权限 28 | * 29 | * @param groupId 分组id 30 | * @return 权限 31 | */ 32 | List selectPermissionsByGroupId(@Param("groupId") Integer groupId); 33 | 34 | /** 35 | * 通过分组ids得到所有分组下的权限 36 | * 37 | * @param groupIds 分组ids 38 | * @param module 权限模块 39 | * @return 权限 40 | */ 41 | List selectPermissionsByGroupIdsAndModule(@Param("groupIds") List groupIds, @Param("module") String module); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/module/log/MDCAccessConstant.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.module.log; 2 | 3 | /** 4 | * 日志常量类 5 | * @author Juzi@TaleLin 6 | * @date 2020/6/20 10:22 7 | */ 8 | public class MDCAccessConstant { 9 | 10 | private MDCAccessConstant() {} 11 | 12 | public static final String REQUEST_METHOD_MDC_KEY = "req.method"; 13 | public static final String RESPONSE_STATUS_MDC_KEY = "res.status"; 14 | public static final String REQUEST_REFERER_MDC_KEY = "req.referer"; 15 | public static final String REQUEST_PROTOCOL_MDC_KEY = "req.protocol"; 16 | public static final String REQUEST_USER_AGENT_MDC_KEY = "req.userAgent"; 17 | public static final String REQUEST_REMOTE_HOST_MDC_KEY = "req.remoteHost"; 18 | public static final String REQUEST_REMOTE_ADDR_MDC_KEY = "req.remoteAddr"; 19 | public static final String REQUEST_REQUEST_URI_MDC_KEY = "req.requestURI"; 20 | public static final String REQUEST_REQUEST_URL_MDC_KEY = "req.requestURL"; 21 | public static final String REQUEST_QUERY_STRING_MDC_KEY = "req.queryString"; 22 | public static final String REQUEST_X_FORWARDED_FOR_MDC_KEY = "req.xForwardedFor"; 23 | public static final String REQUEST_BODY_BYTES_SENT_MDC_KEY = "req.bodyBytesSent"; 24 | public static final String REQUEST_REMOTE_PORT_MDC_KEY = "req.remotePort"; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////// 2 | // _ _ _____ _ __ _____ // 3 | // | | (_) / ____| \/ |/ ____| // 4 | // | | _ _ __ | | | \ / | (___ // 5 | // | | | | '_ \ | | | |\/| |\___ \ // 6 | // | |____| | | | | | |____| | | |____) | // 7 | // |______|_|_| |_| \_____|_| |_|_____/ // 8 | // // 9 | /////////////////////////////////////////////////////////////////////////// 10 | // _______ _______ _ _ _ // 11 | // |__ __| |__ __| | | | | (_) // 12 | // | | ___ __ _ _ __ ___ | | __ _| | ___| | _ _ __ // 13 | // | |/ _ \/ _` | '_ ` _ \ | |/ _` | |/ _ \ | | | '_ \ // 14 | // | | __/ (_| | | | | | | | | (_| | | __/ |____| | | | | // 15 | // |_|\___|\__,_|_| |_| |_| |_|\__,_|_|\___|______|_|_| |_| // 16 | // // 17 | /////////////////////////////////////////////////////////////////////////// -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/configuration/CustomServletRequestDataBinder.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.configuration; 2 | 3 | import com.baomidou.mybatisplus.core.toolkit.StringUtils; 4 | import org.springframework.beans.MutablePropertyValues; 5 | import org.springframework.beans.PropertyValue; 6 | import org.springframework.web.bind.ServletRequestDataBinder; 7 | 8 | import javax.servlet.ServletRequest; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | 12 | /** 13 | * @author Gadfly 14 | * 15 | * 自定义servlet请求参数绑定类 16 | */ 17 | 18 | public class CustomServletRequestDataBinder extends ServletRequestDataBinder { 19 | 20 | public CustomServletRequestDataBinder(final Object target) { 21 | super(target); 22 | } 23 | 24 | @Override 25 | protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) { 26 | List pvs = mpvs.getPropertyValueList(); 27 | List adds = new LinkedList<>(); 28 | for (PropertyValue pv : pvs) { 29 | String name = pv.getName(); 30 | String camel = StringUtils.underlineToCamel(name); 31 | if (!name.equals(camel)) { 32 | adds.add(new PropertyValue(camel, pv.getValue())); 33 | } 34 | } 35 | pvs.addAll(adds); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/Jackson2Test.java: -------------------------------------------------------------------------------- 1 | import com.baomidou.mybatisplus.core.toolkit.StringUtils; 2 | import com.fasterxml.jackson.annotation.JsonFormat; 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | @Slf4j 12 | public class Jackson2Test { 13 | 14 | @Test 15 | public void testMap() throws JsonProcessingException { 16 | ObjectMapper mapper = new ObjectMapper(); 17 | //mapper.setPropertyNamingStrategy(com.fasterxml.jackson.databind.PropertyNamingStrategy.SNAKE_CASE); 18 | //mapper.configOverride(Map.Entry.class).setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.OBJECT)); 19 | mapper.configOverride(Map.Entry.class).setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING)); 20 | Map res = new HashMap<>(); 21 | res.put("username", "pedro"); 22 | res.put("userAge", 24); 23 | String s = mapper.writeValueAsString(res); 24 | log.info(s); 25 | } 26 | 27 | @Test 28 | public void testCamel() throws JsonProcessingException { 29 | String str = "userAge"; 30 | String s = StringUtils.camelToUnderline(str); 31 | log.info(s); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/interceptor/RequestLogInterceptor.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.interceptor; 2 | 3 | import io.github.talelin.latticy.common.util.IPUtil; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.web.servlet.AsyncHandlerInterceptor; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | /** 11 | * @author pedro@TaleLin 12 | * @author colorful@TaleLin 13 | * 请求日志拦截器 14 | */ 15 | @Slf4j 16 | public class RequestLogInterceptor implements AsyncHandlerInterceptor { 17 | 18 | 19 | private final ThreadLocal startTime = new ThreadLocal<>(); 20 | 21 | @Override 22 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { 23 | startTime.set(System.currentTimeMillis()); 24 | return true; 25 | } 26 | 27 | @Override 28 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { 29 | log.info("[{}] -> [{}] from: {} costs: {}ms", 30 | request.getMethod(), 31 | request.getServletPath(), 32 | IPUtil.getIPFromRequest(request), 33 | System.currentTimeMillis() - startTime.get() 34 | ); 35 | startTime.remove(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/mapper/GroupMapper.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import io.github.talelin.latticy.model.GroupDO; 5 | import org.apache.ibatis.annotations.Param; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @author pedro@TaleLin 12 | * @author Juzi@TaleLin 13 | * 分组mapper接口 14 | */ 15 | @Repository 16 | public interface GroupMapper extends BaseMapper { 17 | 18 | /** 19 | * 获得用户的所有分组 20 | * 21 | * @param userId 用户id 22 | * @return 所有分组 23 | */ 24 | List selectGroupsByUserId(@Param("userId") Integer userId); 25 | 26 | /** 27 | * 获得用户的所有分组id 28 | * 29 | * @param userId 用户id 30 | * @return 所有分组id 31 | */ 32 | List selectUserGroupIds(@Param("userId") Integer userId); 33 | 34 | /** 35 | * 通过id获得分组个数 36 | * 37 | * @param id id 38 | * @return 个数 39 | */ 40 | int selectCountById(@Param("id") Integer id); 41 | 42 | /** 43 | * 检查用户是否在该名称的分组里 44 | * 45 | * @param userId 用户id 46 | * @param groupName 分组名 47 | * @return 数量 48 | */ 49 | int selectCountUserByUserIdAndGroupName(@Param("userId") Integer userId, @Param("groupName") String groupName); 50 | } 51 | -------------------------------------------------------------------------------- /src/test/resources/mpg/templates/mapper.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <#if enableCache> 6 | 7 | 8 | 9 | 10 | <#if baseResultMap> 11 | 12 | 13 | <#list table.fields as field> 14 | <#if field.keyFlag><#--生成主键排在第一位--> 15 | 16 | 17 | 18 | <#list table.commonFields as field><#--生成公共字段 --> 19 | <#if field.keyFlag> 20 | 21 | <#else> 22 | 23 | 24 | 25 | <#list table.fields as field> 26 | <#if !field.keyFlag><#--生成普通字段 --> 27 | 28 | 29 | 30 | 31 | 32 | 33 | <#if baseColumnList> 34 | 35 | 36 | <#list table.commonFields as field> 37 | ${field.name}, 38 | 39 | ${table.fieldNames} 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/mapper/LogMapper.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.baomidou.mybatisplus.core.metadata.IPage; 5 | import io.github.talelin.latticy.common.mybatis.LinPage; 6 | import io.github.talelin.latticy.model.LogDO; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.Date; 10 | 11 | /** 12 | * @author pedro@TaleLin 13 | * 日志mapper接口 14 | */ 15 | @Repository 16 | public interface LogMapper extends BaseMapper { 17 | 18 | /** 19 | * 查询日志 20 | * 21 | * @param pager 分页对象 22 | * @param name 用户名 23 | * @param start 开始日期 24 | * @param end 结束日期 25 | * @return 日志分页对象 26 | */ 27 | IPage findLogsByUsernameAndRange(LinPage pager, String name, Date start, Date end); 28 | 29 | /** 30 | * 查询日志 31 | * 32 | * @param pager 分页对象 33 | * @param name 用户名 34 | * @param keyword 查询关键字 35 | * @param start 开始日期 36 | * @param end 结束日期 37 | * @return 日志分页对象 38 | */ 39 | IPage searchLogsByUsernameAndKeywordAndRange(LinPage pager, String name, String keyword, Date start, Date end); 40 | 41 | /** 42 | * 查询用户名分页列表 43 | * 44 | * @param pager Page 45 | * @return 用户名分页对象 46 | */ 47 | IPage getUserNames(LinPage pager); 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/aop/ResultAspect.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.aop; 2 | 3 | import io.github.talelin.latticy.common.configuration.CodeMessageConfiguration; 4 | import io.github.talelin.latticy.vo.UnifyResponseVO; 5 | import org.aspectj.lang.annotation.AfterReturning; 6 | import org.aspectj.lang.annotation.Aspect; 7 | import org.springframework.stereotype.Component; 8 | import org.springframework.util.StringUtils; 9 | 10 | /** 11 | * 处理返回结果为 UnifyResponseVO 的控制器层方法 12 | * message 默认为 null,在此处通过 code 设置为对应消息 13 | * 14 | * @author pedro@TaleLin 15 | * @author colorful@TaleLin 16 | * @author Juzi@TaleLin 17 | */ 18 | @Aspect 19 | @Component 20 | public class ResultAspect { 21 | @AfterReturning(returning = "result", pointcut = "execution(public * io.github.talelin.latticy.controller..*.*(..))") 22 | public void doAfterReturning(UnifyResponseVO result) { 23 | int code = result.getCode(); 24 | String messageOfVO = result.getMessage(); 25 | // code-message.properties 中配置的 message 26 | String messageOfConfiguration = CodeMessageConfiguration.getMessage(code); 27 | 28 | // 如果 code-message.properties 中指定了相应的 message 并且 UnifyResponseVO 的 message 为null 29 | // 则使用 messageOfConfiguration 替换 messageOfVO 30 | if (StringUtils.hasText(messageOfConfiguration) && !StringUtils.hasText(messageOfVO)) { 31 | result.setMessage(messageOfConfiguration); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/controller/cms/FileController.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.controller.cms; 2 | 3 | import io.github.talelin.core.annotation.LoginRequired; 4 | import io.github.talelin.latticy.bo.FileBO; 5 | import io.github.talelin.latticy.service.FileService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.util.MultiValueMap; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | import org.springframework.web.multipart.MultipartFile; 12 | import org.springframework.web.multipart.MultipartHttpServletRequest; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * 文件控制器 18 | * @author pedro@TaleLin 19 | * @author Juzi@TaleLin 20 | */ 21 | @RestController 22 | @RequestMapping("/cms/file") 23 | public class FileController { 24 | 25 | @Autowired 26 | private FileService fileService; 27 | 28 | /** 29 | * 文件上传 30 | * 31 | * @param multipartHttpServletRequest 携带文件的 request 32 | * @return 文件信息 33 | */ 34 | @PostMapping 35 | @LoginRequired 36 | public List upload(MultipartHttpServletRequest multipartHttpServletRequest) { 37 | MultiValueMap fileMap = 38 | multipartHttpServletRequest.getMultiFileMap(); 39 | return fileService.upload(fileMap); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/module/message/MessageWebSocketHandler.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.module.message; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.socket.*; 5 | 6 | /** 7 | * @author pedro@TaleLin 8 | * websocket 消息实现类 9 | */ 10 | public class MessageWebSocketHandler implements WebSocketHandler { 11 | 12 | @Autowired 13 | private WsHandler wsHandler; 14 | 15 | @Override 16 | public void afterConnectionEstablished(WebSocketSession session) throws Exception { 17 | wsHandler.handleOpen(session); 18 | } 19 | 20 | @Override 21 | public void handleMessage(WebSocketSession session, WebSocketMessage webSocketMessage) throws Exception { 22 | if (webSocketMessage instanceof TextMessage) { 23 | TextMessage textMessage = (TextMessage) webSocketMessage; 24 | wsHandler.handleMessage(session, textMessage.getPayload()); 25 | } 26 | } 27 | 28 | @Override 29 | public void handleTransportError(WebSocketSession session, Throwable throwable) throws Exception { 30 | wsHandler.handleError(session, throwable); 31 | } 32 | 33 | @Override 34 | public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { 35 | wsHandler.handleClose(session); 36 | } 37 | 38 | @Override 39 | public boolean supportsPartialMessages() { 40 | return false; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/LatticyApplication.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy; 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.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | /** 10 | * @author pedro@TaleLin 11 | * 启动入口类 12 | */ 13 | @RestController 14 | @MapperScan(basePackages = {"io.github.talelin.latticy.mapper"}) 15 | @SpringBootApplication(scanBasePackages = {"io.github.talelin.latticy"}) 16 | public class LatticyApplication { 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(LatticyApplication.class, args); 20 | } 21 | 22 | @RequestMapping("/") 23 | public String index() { 24 | return "

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

"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/mapper/BookMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 26 | 27 | 34 | -------------------------------------------------------------------------------- /src/test/java/io/github/talelin/latticy/mapper/FileMapperTest.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.mapper; 2 | 3 | import io.github.talelin.latticy.model.FileDO; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.TestInstance; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.annotation.Rollback; 10 | import org.springframework.test.context.ActiveProfiles; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertEquals; 14 | 15 | 16 | @SpringBootTest 17 | @Transactional 18 | @Rollback 19 | @ActiveProfiles("test") 20 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 21 | public class FileMapperTest { 22 | 23 | @Autowired 24 | private FileMapper fileMapper; 25 | 26 | private final String md5 = "iiiiilllllll"; 27 | private final String name = "千里之外"; 28 | 29 | @BeforeAll 30 | public void setUp() throws Exception { 31 | FileDO fileDO = new FileDO(); 32 | fileDO.setName(name); 33 | fileDO.setPath("千里之外..."); 34 | fileDO.setSize(1111); 35 | fileDO.setExtension(".png"); 36 | fileDO.setMd5(md5); 37 | fileMapper.insert(fileDO); 38 | } 39 | 40 | @Test 41 | public void testFindOneByMd5() { 42 | FileDO one = fileMapper.selectByMd5(md5); 43 | assertEquals(one.getName(), name); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /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 | 20 | 27 | 28 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/configuration/CustomServletModelAttributeMethodProcessor.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.configuration; 2 | 3 | import org.springframework.util.Assert; 4 | import org.springframework.web.bind.ServletRequestDataBinder; 5 | import org.springframework.web.bind.WebDataBinder; 6 | import org.springframework.web.context.request.NativeWebRequest; 7 | import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor; 8 | 9 | import javax.servlet.ServletRequest; 10 | 11 | /** 12 | * @author Gadfly 13 | * 14 | * A customizing Servlet-specific ModelAttributeMethodProcessor that applies data binding through 15 | * a WebDataBinder of type CustomServletModelAttributeMethodProcessor. 16 | */ 17 | public class CustomServletModelAttributeMethodProcessor extends ServletModelAttributeMethodProcessor { 18 | 19 | public CustomServletModelAttributeMethodProcessor(final boolean annotationNotRequired) { 20 | super(annotationNotRequired); 21 | } 22 | 23 | @Override 24 | protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) { 25 | ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class); 26 | Assert.state(servletRequest != null, "No ServletRequest"); 27 | ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder; 28 | 29 | // ServletModelAttributeMethodProcessor 此处使用的 servletBinder.bind(servletRequest) 30 | // 修改的目的是为了将 ServletRequestDataBinder 换成自定的 CustomServletRequestDataBinder 31 | new CustomServletRequestDataBinder(servletBinder.getTarget()).bind(servletRequest); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/service/PermissionService.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import io.github.talelin.latticy.model.PermissionDO; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * @author pedro@TaleLin 11 | * @author Juzi@TaleLin 12 | * 权限服务接口 13 | */ 14 | public interface PermissionService extends IService { 15 | 16 | /** 17 | * 通过分组id得到分组的权限 18 | * 19 | * @param groupId 分组id 20 | * @return 权限 21 | */ 22 | List getPermissionByGroupId(Integer groupId); 23 | 24 | /** 25 | * 通过分组id得到分组的权限 26 | * 27 | * @param groupIds 分组id 28 | * @return 权限 29 | */ 30 | List getPermissionByGroupIds(List groupIds); 31 | 32 | /** 33 | * 通过分组id得到分组的权限与分组id的映射 34 | * 35 | * @param groupIds 分组id 36 | * @return 权限map 37 | */ 38 | Map> getPermissionMapByGroupIds(List groupIds); 39 | 40 | /** 41 | * 将权限结构化 42 | * 43 | * @param permissions 权限 44 | * @return 结构化的权限 45 | */ 46 | List>>> structuringPermissions(List permissions); 47 | 48 | /** 49 | * 将权限简单地结构化 50 | * 51 | * @param permissions 权限 52 | * @return 结构化的权限 53 | */ 54 | Map> structuringPermissionsSimply(List permissions); 55 | 56 | /** 57 | * 通过分组id和权限模块得到分组的权限与分组id的映射 58 | * 59 | * @param groupIds 分组id 60 | * @param module 权限模块 61 | * @return 权限map 62 | */ 63 | List getPermissionByGroupIdsAndModule(List groupIds, String module); 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/util/IPUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.web.context.request.RequestContextHolder; 5 | import org.springframework.web.context.request.ServletRequestAttributes; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | 9 | /** 10 | * @author Gadfy 11 | * IP工具类 12 | */ 13 | @Slf4j 14 | public class IPUtil { 15 | 16 | private IPUtil() { 17 | throw new IllegalStateException("Utility class"); 18 | } 19 | 20 | private static final String[] IP_HEADER_CANDIDATES = { 21 | "X-Forwarded-For", 22 | "Proxy-Client-IP", 23 | "WL-Proxy-Client-IP", 24 | "HTTP_X_FORWARDED_FOR", 25 | "HTTP_X_FORWARDED", 26 | "HTTP_X_CLUSTER_CLIENT_IP", 27 | "HTTP_CLIENT_IP", 28 | "HTTP_FORWARDED_FOR", 29 | "HTTP_FORWARDED", 30 | "HTTP_VIA", 31 | "REMOTE_ADDR" 32 | }; 33 | 34 | public static String getIPFromRequest(HttpServletRequest request) { 35 | if (request == null) { 36 | if (null == RequestContextHolder.getRequestAttributes()) { 37 | return null; 38 | } 39 | request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 40 | } 41 | 42 | for (String header : IP_HEADER_CANDIDATES) { 43 | String ipList = request.getHeader(header); 44 | if (ipList != null && ipList.length() != 0 && !"unknown".equalsIgnoreCase(ipList)) { 45 | log.debug("ipList.split(\",\")[0]::{}", ipList.split(",")[0]); 46 | return ipList.split(",")[0]; 47 | } 48 | } 49 | 50 | log.debug("request.getRemoteAddr()::{}", request.getRemoteAddr()); 51 | return request.getRemoteAddr(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/mybatis/LinPage.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.mybatis; 2 | 3 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 4 | 5 | /** 6 | * 分页对象 7 | * 为和其他端保持一致 8 | * 重写 MyBatis-Plus 分页对象,将起始页从 1 改为 0 9 | * 10 | * @author Juzi@TaleLin 11 | */ 12 | public class LinPage extends Page { 13 | 14 | private static final long serialVersionUID = -2183463672525305273L; 15 | 16 | /** 17 | * 该构造方法使得 current 总为 0 18 | */ 19 | public LinPage() { 20 | super.setCurrent(0); 21 | } 22 | 23 | public LinPage(int current, int size) { 24 | this(current, size, 0); 25 | } 26 | 27 | public LinPage(int current, int size, int total) { 28 | this(current, size, total, true); 29 | } 30 | 31 | public LinPage(int current, int size, boolean isSearchCount) { 32 | this(current, size, 0, isSearchCount); 33 | } 34 | 35 | /** 36 | * 该构造方法将小于 0 的 current 置为 0 37 | * 38 | * @param current 当前页 39 | * @param size 每页显示条数,默认 10 40 | * @param total 总数 41 | * @param isSearchCount 是否进行 count 查询 42 | */ 43 | public LinPage(int current, int size, int total, boolean isSearchCount) { 44 | super(current, size, total, isSearchCount); 45 | 46 | if (current < 0) { 47 | current = 0; 48 | } 49 | super.setCurrent(current); 50 | } 51 | 52 | @Override 53 | public boolean hasPrevious() { 54 | return super.getCurrent() > 0; 55 | } 56 | 57 | @Override 58 | public boolean hasNext() { 59 | return super.getCurrent() + 1 < this.getPages(); 60 | } 61 | 62 | /** 63 | * 重写计算偏移量,将分页从第 0 开始 64 | * 65 | * @return 偏移量 66 | */ 67 | @Override 68 | public long offset() { 69 | return getCurrent() > 0 ? super.getCurrent() * getSize() : 0; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/resources/mapper/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 25 | 26 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/configuration/LoginCaptchaProperties.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.configuration; 2 | 3 | import io.github.talelin.latticy.common.util.CaptchaUtil; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.util.StringUtils; 10 | 11 | /** 12 | * @author Gadfly 13 | * 14 | * 登录图形验证码配置类 15 | * 16 | */ 17 | @Slf4j 18 | @Getter 19 | @Setter 20 | @Component 21 | @ConfigurationProperties(prefix = "login-captcha") 22 | public class LoginCaptchaProperties { 23 | /** 24 | * aes 密钥 25 | */ 26 | private String secret = CaptchaUtil.getRandomString(32); 27 | /** 28 | * aes 偏移量 29 | */ 30 | private String iv = CaptchaUtil.getRandomString(16); 31 | /** 32 | * 启用验证码 33 | */ 34 | private Boolean enabled = Boolean.FALSE; 35 | 36 | public void setSecret(String secret) { 37 | final long ivLen1 = 16; 38 | final long ivLen2 = 24; 39 | final long ivLen3 = 32; 40 | if (StringUtils.hasText(secret)) { 41 | byte[] bytes = secret.getBytes(); 42 | if (bytes.length == ivLen1 || bytes.length == ivLen2 || bytes.length == ivLen3) { 43 | this.secret = secret; 44 | } else { 45 | log.warn("AES密钥必须为128/192/256bit,输入的密钥为{}bit,已启用随机密钥{}", bytes.length * 8, this.secret); 46 | } 47 | } 48 | } 49 | 50 | public void setIv(String iv) { 51 | final long ivLen = 16; 52 | if (StringUtils.hasText(iv)) { 53 | byte[] bytes = iv.getBytes(); 54 | if (bytes.length == ivLen) { 55 | this.iv = iv; 56 | } else { 57 | log.warn("AES初始向量必须为128bit,输入的密钥为{}bit,已启用随机向量{}", bytes.length * 8, this.iv); 58 | } 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/service/LogService.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import io.github.talelin.latticy.model.LogDO; 6 | 7 | import java.util.Date; 8 | 9 | /** 10 | * @author pedro@TaleLin 11 | * @author Juzi@TaleLin 12 | * 日志服务接口 13 | */ 14 | public interface LogService extends IService { 15 | 16 | /** 17 | * 分页获取日志 18 | * 19 | * @param page 当前页 20 | * @param count 当前页数目 21 | * @param name 用户名 22 | * @param start 日志开启时间 23 | * @param end 日志结束时间 24 | * @return 日志数据 25 | */ 26 | IPage getLogPage(Integer page, Integer count, String name, Date start, Date end); 27 | 28 | /** 29 | * 分页搜索日志 30 | * 31 | * @param page 当前页 32 | * @param count 当前页数目 33 | * @param name 用户名 34 | * @param keyword 搜索关键字 35 | * @param start 日志开启时间 36 | * @param end 日志结束时间 37 | * @return 日志数据 38 | */ 39 | IPage searchLogPage(Integer page, Integer count, String name, String keyword, Date start, Date end); 40 | 41 | /** 42 | * 分页获取日志用户名(以被记录日志的用户) 43 | * 44 | * @param page 当前页 45 | * @param count 当前页数目 46 | * @return 用户名 47 | */ 48 | IPage getUserNamePage(Integer page, Integer count); 49 | 50 | /** 51 | * 创建一条日志记录 52 | * 53 | * @param message 日志消息 54 | * @param permission 日志涉及的权限 55 | * @param userId 用户 id 56 | * @param username 用户名 57 | * @param method 请求(http)方法 58 | * @param path 请求路径 59 | * @param status 相应状态(http status) 60 | * @return 是否成功 61 | */ 62 | boolean createLog(String message, String permission, Integer userId, 63 | String username, String method, String path, 64 | Integer status); 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/service/impl/BookServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.service.impl; 2 | 3 | import io.github.talelin.latticy.dto.book.CreateOrUpdateBookDTO; 4 | import io.github.talelin.latticy.mapper.BookMapper; 5 | import io.github.talelin.latticy.model.BookDO; 6 | import io.github.talelin.latticy.service.BookService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * @author pedro@TaleLin 14 | * @author Juzi@TaleLin 15 | * 图书服务实现类 16 | */ 17 | @Service 18 | public class BookServiceImpl implements BookService { 19 | 20 | @Autowired 21 | private BookMapper bookMapper; 22 | 23 | @Override 24 | public boolean createBook(CreateOrUpdateBookDTO validator) { 25 | BookDO book = new BookDO(); 26 | book.setAuthor(validator.getAuthor()); 27 | book.setTitle(validator.getTitle()); 28 | book.setImage(validator.getImage()); 29 | book.setSummary(validator.getSummary()); 30 | return bookMapper.insert(book) > 0; 31 | } 32 | 33 | @Override 34 | public List getBookByKeyword(String q) { 35 | return bookMapper.selectByTitleLikeKeyword(q); 36 | } 37 | 38 | @Override 39 | public boolean updateBook(BookDO book, CreateOrUpdateBookDTO validator) { 40 | book.setAuthor(validator.getAuthor()); 41 | book.setTitle(validator.getTitle()); 42 | book.setImage(validator.getImage()); 43 | book.setSummary(validator.getSummary()); 44 | return bookMapper.updateById(book) > 0; 45 | } 46 | 47 | @Override 48 | public BookDO getById(Integer id) { 49 | return bookMapper.selectById(id); 50 | } 51 | 52 | @Override 53 | public List findAll() { 54 | return bookMapper.selectList(null); 55 | } 56 | 57 | @Override 58 | public boolean deleteById(Integer id) { 59 | return bookMapper.deleteById(id) > 0; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/module/message/WebsocketConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.module.message; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.web.socket.config.annotation.EnableWebSocket; 8 | import org.springframework.web.socket.config.annotation.WebSocketConfigurer; 9 | import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; 10 | 11 | /** 12 | * @author pedro@TaleLin 13 | * websocket 配置类 14 | */ 15 | @Configuration 16 | @ConditionalOnProperty(prefix = "lin.cms.websocket", value = "enable", havingValue = "true") 17 | @EnableWebSocket 18 | public class WebsocketConfiguration implements WebSocketConfigurer { 19 | 20 | @Value("${lin.cms.websocket.intercept:false}") 21 | private boolean intercepted; 22 | 23 | @Bean 24 | public MessageWebSocketHandler messageWebSocketHandler() { 25 | return new MessageWebSocketHandler(); 26 | } 27 | 28 | @Bean 29 | public WsHandler wsHandler() { 30 | return new WsHandlerImpl(); 31 | } 32 | 33 | @Bean 34 | @ConditionalOnProperty(prefix = "lin.cms.websocket", value = "intercept", havingValue = "true") 35 | public WebSocketInterceptor webSocketInterceptor() { 36 | return new WebSocketInterceptor(); 37 | } 38 | 39 | @Override 40 | public void registerWebSocketHandlers(WebSocketHandlerRegistry handlerRegistry) { 41 | if (intercepted) { 42 | handlerRegistry.addHandler(messageWebSocketHandler(), "ws/message") 43 | .addInterceptors(webSocketInterceptor()) 44 | .setAllowedOrigins("*"); 45 | } else { 46 | handlerRegistry.addHandler(messageWebSocketHandler(), "ws/message") 47 | .setAllowedOrigins("*"); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/vo/UnifyResponseVO.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.vo; 2 | 3 | import io.github.talelin.autoconfigure.bean.Code; 4 | import io.github.talelin.autoconfigure.util.RequestUtil; 5 | import io.github.talelin.latticy.common.util.ResponseUtil; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Data; 9 | import org.springframework.http.HttpStatus; 10 | 11 | 12 | /** 13 | * 统一API响应结果封装视图对象 14 | * 15 | * @author pedro@TaleLin 16 | * @author colorful@TaleLin 17 | * @author Juzi@TaleLin 18 | */ 19 | @Data 20 | @Builder 21 | @AllArgsConstructor 22 | public class UnifyResponseVO { 23 | 24 | private Integer code; 25 | 26 | private T message; 27 | 28 | private String request; 29 | 30 | public UnifyResponseVO() { 31 | this.code = Code.SUCCESS.getCode(); 32 | this.request = RequestUtil.getSimpleRequest(); 33 | } 34 | 35 | public UnifyResponseVO(int code) { 36 | this.code = code; 37 | this.request = RequestUtil.getSimpleRequest(); 38 | } 39 | 40 | public UnifyResponseVO(T message) { 41 | this.code = Code.SUCCESS.getCode(); 42 | this.message = message; 43 | this.request = RequestUtil.getSimpleRequest(); 44 | } 45 | 46 | public UnifyResponseVO(int code, T message) { 47 | this.code = code; 48 | this.message = message; 49 | this.request = RequestUtil.getSimpleRequest(); 50 | } 51 | 52 | public UnifyResponseVO(T message, HttpStatus httpStatus) { 53 | this.code = Code.SUCCESS.getCode(); 54 | this.message = message; 55 | this.request = RequestUtil.getSimpleRequest(); 56 | ResponseUtil.setCurrentResponseHttpStatus(httpStatus.value()); 57 | } 58 | 59 | public UnifyResponseVO(int code, T message, HttpStatus httpStatus) { 60 | this.code = code; 61 | this.message = message; 62 | this.request = RequestUtil.getSimpleRequest(); 63 | ResponseUtil.setCurrentResponseHttpStatus(httpStatus.value()); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/io/github/talelin/latticy/mapper/BookMapperTest.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.mapper; 2 | 3 | import io.github.talelin.latticy.model.BookDO; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.annotation.Rollback; 8 | import org.springframework.test.context.ActiveProfiles; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import java.util.List; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertEquals; 14 | import static org.junit.jupiter.api.Assertions.assertTrue; 15 | 16 | 17 | @SpringBootTest 18 | @Transactional 19 | @Rollback 20 | @ActiveProfiles("test") 21 | public class BookMapperTest { 22 | 23 | @Autowired 24 | private BookMapper bookMapper; 25 | 26 | public BookDO initData() { 27 | String title = "千里之外"; 28 | String author = "pedro"; 29 | String image = "千里之外.png"; 30 | String summary = "千里之外,是周杰伦和费玉清一起发售的歌曲"; 31 | BookDO bookDO = new BookDO(); 32 | bookDO.setTitle(title); 33 | bookDO.setAuthor(author); 34 | bookDO.setImage(image); 35 | bookDO.setSummary(summary); 36 | bookMapper.insert(bookDO); 37 | return bookDO; 38 | } 39 | 40 | 41 | @Test 42 | public void selectByTitleLikeKeyword() { 43 | BookDO book = initData(); 44 | List found = bookMapper.selectByTitleLikeKeyword("%千里%"); 45 | boolean anyMatch = found.stream().anyMatch(it -> it.getTitle().equals(book.getTitle())); 46 | assertTrue(anyMatch); 47 | } 48 | 49 | 50 | @Test 51 | public void selectById() { 52 | BookDO book = initData(); 53 | BookDO found = bookMapper.selectById(book.getId()); 54 | assertEquals(found.getTitle(), book.getTitle()); 55 | } 56 | 57 | @Test 58 | public void selectByTitle() { 59 | BookDO book = initData(); 60 | List found = bookMapper.selectByTitle(book.getTitle()); 61 | boolean anyMatch = found.stream().anyMatch(it -> it.getTitle().equals(book.getTitle())); 62 | assertTrue(anyMatch); 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/service/impl/LogServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 5 | import io.github.talelin.latticy.common.mybatis.LinPage; 6 | import io.github.talelin.latticy.mapper.LogMapper; 7 | import io.github.talelin.latticy.model.LogDO; 8 | import io.github.talelin.latticy.service.LogService; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.Date; 12 | 13 | /** 14 | * @author pedro@TaleLin 15 | * @author Juzi@TaleLin 16 | * 日志服务实现类 17 | */ 18 | @Service 19 | public class LogServiceImpl extends ServiceImpl implements LogService { 20 | 21 | @Override 22 | public IPage getLogPage(Integer page, Integer count, String name, Date start, Date end) { 23 | LinPage pager = new LinPage<>(page, count); 24 | return this.baseMapper.findLogsByUsernameAndRange(pager, name, start, end); 25 | } 26 | 27 | @Override 28 | public IPage searchLogPage(Integer page, Integer count, String name, String keyword, Date start, Date end) { 29 | LinPage pager = new LinPage<>(page, count); 30 | return this.baseMapper.searchLogsByUsernameAndKeywordAndRange(pager, name, "%" + keyword + "%", start, end); 31 | } 32 | 33 | @Override 34 | public IPage getUserNamePage(Integer page, Integer count) { 35 | LinPage pager = new LinPage<>(page, count); 36 | return this.baseMapper.getUserNames(pager); 37 | } 38 | 39 | @Override 40 | public boolean createLog(String message, String permission, Integer userId, String username, String method, String path, Integer status) { 41 | LogDO log = LogDO.builder() 42 | .message(message) 43 | .userId(userId) 44 | .username(username) 45 | .statusCode(status) 46 | .method(method) 47 | .path(path) 48 | .build(); 49 | if (permission != null) { 50 | log.setPermission(permission); 51 | } 52 | return this.baseMapper.insert(log) > 0; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/module/file/FileUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.module.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 | /** 12 | * @author pedro@TaleLin 13 | * @author colorful@TaleLin 14 | * 文件工具类 15 | */ 16 | public class FileUtil { 17 | 18 | private FileUtil() {} 19 | 20 | public static FileSystem getDefaultFileSystem() { 21 | return FileSystems.getDefault(); 22 | } 23 | 24 | public static boolean isAbsolute(String str) { 25 | Path path = getDefaultFileSystem().getPath(str); 26 | return path.isAbsolute(); 27 | } 28 | 29 | @SuppressWarnings("ResultOfMethodCallIgnored") 30 | public static void initStoreDir(String dir) { 31 | String absDir; 32 | if (isAbsolute(dir)) { 33 | absDir = dir; 34 | } else { 35 | String cmd = getCmd(); 36 | Path path = getDefaultFileSystem().getPath(cmd, dir); 37 | absDir = path.toAbsolutePath().toString(); 38 | } 39 | File file = new File(absDir); 40 | if (!file.exists()) { 41 | file.mkdirs(); 42 | } 43 | } 44 | 45 | public static String getCmd() { 46 | return System.getProperty("user.dir"); 47 | } 48 | 49 | public static String getFileAbsolutePath(String dir, String filename) { 50 | if (isAbsolute(dir)) { 51 | return getDefaultFileSystem() 52 | .getPath(dir, filename) 53 | .toAbsolutePath().toString(); 54 | } else { 55 | return getDefaultFileSystem() 56 | .getPath(getCmd(), dir, filename) 57 | .toAbsolutePath().toString(); 58 | } 59 | } 60 | 61 | public static String getFileExt(String filename) { 62 | int index = filename.lastIndexOf('.'); 63 | return filename.substring(index); 64 | } 65 | 66 | public static String getFileMD5(byte[] bytes) { 67 | return DigestUtils.md5DigestAsHex(bytes); 68 | } 69 | 70 | public static Long parseSize(String size) { 71 | DataSize singleLimitData = DataSize.parse(size); 72 | return singleLimitData.toBytes(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /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 | 21 | 32 | 33 | 47 | 48 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/controller/cms/LogController.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.controller.cms; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import io.github.talelin.core.annotation.GroupRequired; 5 | import io.github.talelin.core.annotation.PermissionMeta; 6 | import io.github.talelin.core.annotation.PermissionModule; 7 | import io.github.talelin.latticy.common.util.PageUtil; 8 | import io.github.talelin.latticy.dto.log.QueryLogDTO; 9 | import io.github.talelin.latticy.dto.query.BasePageDTO; 10 | import io.github.talelin.latticy.model.LogDO; 11 | import io.github.talelin.latticy.service.LogService; 12 | import io.github.talelin.latticy.vo.PageResponseVO; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.validation.annotation.Validated; 15 | import org.springframework.web.bind.annotation.GetMapping; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RestController; 18 | 19 | /** 20 | * 日志控制器 21 | * @author pedro@TaleLin 22 | * @author Juzi@TaleLin 23 | */ 24 | @RestController 25 | @RequestMapping("/cms/log") 26 | @PermissionModule(value = "日志") 27 | @Validated 28 | public class LogController { 29 | @Autowired 30 | private LogService logService; 31 | 32 | @GetMapping("") 33 | @GroupRequired 34 | @PermissionMeta(value = "查询所有日志") 35 | public PageResponseVO getLogs(QueryLogDTO dto) { 36 | IPage iPage = logService.getLogPage( 37 | dto.getPage(), dto.getCount(), 38 | dto.getName(), dto.getStart(), 39 | dto.getEnd() 40 | ); 41 | return PageUtil.build(iPage); 42 | } 43 | 44 | @GetMapping("/search") 45 | @GroupRequired 46 | @PermissionMeta(value = "搜索日志") 47 | public PageResponseVO searchLogs(QueryLogDTO dto) { 48 | IPage iPage = logService.searchLogPage( 49 | dto.getPage(), dto.getCount(), 50 | dto.getName(), dto.getKeyword(), 51 | dto.getStart(), dto.getEnd() 52 | ); 53 | return PageUtil.build(iPage); 54 | } 55 | 56 | @GetMapping("/users") 57 | @GroupRequired 58 | @PermissionMeta(value = "查询日志记录的用户") 59 | public PageResponseVO getUsers(@Validated BasePageDTO dto) { 60 | IPage iPage = logService.getUserNamePage(dto.getPage(), dto.getCount()); 61 | return PageUtil.build(iPage); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/module/file/FileProperties.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.module.file; 2 | 3 | import io.github.talelin.latticy.common.factory.YamlPropertySourceFactory; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.context.annotation.PropertySource; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * @author pedro@TaleLin 10 | * 文件配置类 11 | */ 12 | @Component 13 | @ConfigurationProperties("lin.file") 14 | @PropertySource( 15 | value = "classpath:io/github/talelin/latticy/extension/file/config.yml", 16 | encoding = "UTF-8", factory = YamlPropertySourceFactory.class) 17 | public class FileProperties { 18 | 19 | private static final String[] DEFAULT_EMPTY_ARRAY = new String[0]; 20 | 21 | private String storeDir = "/assets"; 22 | 23 | private String singleLimit = "2MB"; 24 | 25 | private Integer nums = 10; 26 | 27 | private String domain; 28 | 29 | private String[] exclude = DEFAULT_EMPTY_ARRAY; 30 | 31 | private String[] include = DEFAULT_EMPTY_ARRAY; 32 | 33 | /** 34 | * 文件存储路径 35 | */ 36 | private String servePath = "assets/**"; 37 | 38 | public String getServePath() { 39 | return servePath; 40 | } 41 | 42 | public void setServePath(String servePath) { 43 | this.servePath = servePath; 44 | } 45 | 46 | public String getStoreDir() { 47 | return storeDir; 48 | } 49 | 50 | public void setStoreDir(String storeDir) { 51 | this.storeDir = storeDir; 52 | } 53 | 54 | public String getSingleLimit() { 55 | return singleLimit; 56 | } 57 | 58 | public void setSingleLimit(String singleLimit) { 59 | this.singleLimit = singleLimit; 60 | } 61 | 62 | public Integer getNums() { 63 | return nums; 64 | } 65 | 66 | public void setNums(Integer nums) { 67 | this.nums = nums; 68 | } 69 | 70 | public String[] getExclude() { 71 | return exclude; 72 | } 73 | 74 | public void setExclude(String[] exclude) { 75 | this.exclude = exclude; 76 | } 77 | 78 | public String[] getInclude() { 79 | return include; 80 | } 81 | 82 | public void setInclude(String[] include) { 83 | this.include = include; 84 | } 85 | 86 | public String getDomain() { 87 | return domain; 88 | } 89 | 90 | public void setDomain(String domain) { 91 | this.domain = domain; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/service/UserIdentityService.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import io.github.talelin.latticy.model.UserIdentityDO; 5 | 6 | /** 7 | * @author pedro@TaleLin 8 | * @author Juzi@TaleLin 9 | * 用户身份标识服务接口 10 | */ 11 | public interface UserIdentityService extends IService { 12 | 13 | /** 14 | * 新建用户认证信息 15 | * 16 | * @param userId 用户id 17 | * @param identityType 认证类型 18 | * @param identifier 标识 19 | * @param credential 凭证 20 | * @return 用户认证 21 | */ 22 | UserIdentityDO createIdentity(Integer userId, 23 | String identityType, 24 | String identifier, 25 | String credential); 26 | 27 | /** 28 | * 新建用户认证信息 29 | * 30 | * @param userIdentity 用户认证信息 31 | * @return 用户认证 32 | */ 33 | UserIdentityDO createIdentity(UserIdentityDO userIdentity); 34 | 35 | /** 36 | * 新建用户认证信息 (USERNAME_PASSWORD) 37 | * 38 | * @param userId 用户id 39 | * @param username 用户名 40 | * @param password 密码 41 | * @return 用户认证 42 | */ 43 | UserIdentityDO createUsernamePasswordIdentity(Integer userId, 44 | String username, 45 | String password); 46 | 47 | 48 | /** 49 | * 验证用户认证信息 (USERNAME_PASSWORD) 50 | * 51 | * @param userId 用户id 52 | * @param username 用户名 53 | * @param password 密码 54 | * @return 是否验证成功 55 | */ 56 | boolean verifyUsernamePassword(Integer userId, String username, String password); 57 | 58 | /** 59 | * 修改密码 60 | * 61 | * @param userId 用户id 62 | * @param password 新密码 63 | * @return 是否成功 64 | */ 65 | boolean changePassword(Integer userId, String password); 66 | 67 | /** 68 | * 修改用户名 69 | * 70 | * @param userId 用户id 71 | * @param username 新用户名 72 | * @return 是否成功 73 | */ 74 | boolean changeUsername(Integer userId, String username); 75 | 76 | /** 77 | * 修改用户名密码 78 | * 79 | * @param userId 用户id 80 | * @param username 新用户名 81 | * @param password 新密码 82 | * @return 是否成功 83 | */ 84 | boolean changeUsernamePassword(Integer userId, String username, String password); 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/extension/file/LocalUploader.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.extension.file; 2 | 3 | import io.github.talelin.latticy.module.file.AbstractUploader; 4 | import io.github.talelin.latticy.module.file.FileProperties; 5 | import io.github.talelin.latticy.module.file.FileTypeEnum; 6 | import io.github.talelin.latticy.module.file.FileUtil; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | 10 | import javax.annotation.PostConstruct; 11 | import java.io.BufferedOutputStream; 12 | import java.io.File; 13 | import java.nio.file.Files; 14 | import java.nio.file.Path; 15 | import java.nio.file.Paths; 16 | import java.text.SimpleDateFormat; 17 | import java.util.Date; 18 | 19 | /** 20 | * 文件上传服务默认实现,上传到本地 21 | * 22 | * @author pedro@TaleLin 23 | */ 24 | @Slf4j 25 | public class LocalUploader extends AbstractUploader { 26 | 27 | @Autowired 28 | private FileProperties fileProperties; 29 | 30 | 31 | @PostConstruct 32 | public void initStoreDir() { 33 | // 本地存储需先初始化存储文件夹 34 | FileUtil.initStoreDir(this.fileProperties.getStoreDir()); 35 | } 36 | 37 | @Override 38 | protected boolean handleOneFile(byte[] bytes, String newFilename) { 39 | String absolutePath = 40 | FileUtil.getFileAbsolutePath(fileProperties.getStoreDir(), getStorePath(newFilename)); 41 | try (BufferedOutputStream stream = 42 | new BufferedOutputStream(Files.newOutputStream(new File(absolutePath).toPath()))) { 43 | stream.write(bytes); 44 | } catch (Exception e) { 45 | log.error("write file to local err:", e); 46 | return false; 47 | } 48 | return true; 49 | } 50 | 51 | @Override 52 | protected FileProperties getFileProperties() { 53 | return fileProperties; 54 | } 55 | 56 | @SuppressWarnings("ResultOfMethodCallIgnored") 57 | @Override 58 | protected String getStorePath(String newFilename) { 59 | Date now = new Date(); 60 | String format = new SimpleDateFormat("yyyy/MM/dd").format(now); 61 | Path path = Paths.get(fileProperties.getStoreDir(), format).toAbsolutePath(); 62 | java.io.File file = new File(path.toString()); 63 | if (!file.exists()) { 64 | file.mkdirs(); 65 | } 66 | return Paths.get(format, newFilename).toString(); 67 | } 68 | 69 | @Override 70 | protected String getFileType() { 71 | return FileTypeEnum.LOCAL.getValue(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/resources/mapper/GroupMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 34 | 35 | 51 | 52 | 55 | 56 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/main/resources/ValidationMessages.properties: -------------------------------------------------------------------------------- 1 | # \u901A\u7528\u5F02\u5E38\u4FE1\u606F 2 | id.positive=id\u5FC5\u987B\u4E3A\u6B63\u6574\u6570 3 | page.count.min=\u5206\u9875\u6570\u91CF\u5FC5\u987B\u4E3A\u6B63\u6574\u6570 4 | page.count.max=\u5206\u9875\u6570\u91CF\u5FC5\u987B\u5C0F\u4E8E{value} 5 | page.number.min=\u5206\u9875\u9875\u7801\u5FC5\u987B\u4E3A\u6B63\u6574\u6570 6 | # \u5206\u7EC4\u5F02\u5E38\u4FE1\u606F 7 | group.id.positive=\u5206\u7EC4id\u5FC5\u987B\u4E3A\u6B63\u6574\u6570 8 | group.id.not-null=\u5206\u7EC4id\u4E0D\u53EF\u4E3A\u7A7A 9 | group.ids.long-list=\u5206\u7EC4id\u6BCF\u4E00\u9879\u5FC5\u987B\u4E3A\u6B63\u6574\u6570 10 | group.ids.not-empty=\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A\u5206\u7EC4 11 | group.name.not-blank=\u8BF7\u8F93\u5165\u5206\u7EC4\u540D\u79F0 12 | group.name.length=\u5206\u7EC4\u540D\u79F0\u4E0D\u53EF\u8D85\u8FC760\u5B57\u7B26 13 | group.info.length=\u5206\u7EC4\u63CF\u8FF0\u4E0D\u53EF\u8D85\u8FC7255\u5B57\u7B26 14 | # \u6743\u9650\u5F02\u5E38\u4FE1\u606F 15 | permission.ids.long-list=\u8BF7\u8F93\u5165\u6743\u9650id\uFF0C\u4E14\u6BCF\u4E00\u9879\u4E0D\u53EF\u4E3A\u7A7A 16 | permission.id.positive=\u6743\u9650id\u5FC5\u987B\u6B63\u6574\u6570 17 | permission.id.not-null=\u6743\u9650id\u4E0D\u53EF\u4E3A\u7A7A 18 | email=\u7535\u5B50\u90AE\u7BB1\u4E0D\u7B26\u5408\u89C4\u8303\uFF0C\u8BF7\u8F93\u5165\u6B63\u786E\u7684\u90AE\u7BB1 19 | # \u7528\u6237\u5F02\u5E38\u4FE1\u606F 20 | username.not-blank=\u7528\u6237\u540D\u4E0D\u53EF\u4E3A\u7A7A 21 | username.length=\u7528\u6237\u540D\u957F\u5EA6\u5FC5\u987B\u57282~10\u4E4B\u95F4 22 | nickname.length=\u7528\u6237\u540D\u957F\u5EA6\u5FC5\u987B\u57282~10\u4E4B\u95F4 23 | avatar.length=\u5934\u50CF\u94FE\u63A5\u957F\u5EA6\u4E0D\u80FD\u8D85\u8FC7500\u5B57\u7B26 24 | password.new.not-blank=\u5BC6\u7801\u4E0D\u53EF\u4E3A\u7A7A 25 | password.new.pattern=\u5BC6\u7801\u957F\u5EA6\u5FC5\u987B\u57286~22\u4F4D\u4E4B\u95F4\uFF0C\u5305\u542B\u5B57\u7B26\u3001\u6570\u5B57\u548C _ 26 | password.confirm.not-blank=\u786E\u8BA4\u5BC6\u7801\u4E0D\u53EF\u4E3A\u7A7A 27 | password.old.not-blank=\u65E7\u5BC6\u7801\u4E0D\u53EF\u4E3A\u7A7A 28 | password.equal-field=\u4E24\u6B21\u8F93\u5165\u5BC6\u7801\u4E0D\u4E00\u81F4 29 | captcha.not-blank=\u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A 30 | # \u56FE\u4E66\u5F02\u5E38\u4FE1\u606F 31 | book.title.not-empty=\u5FC5\u987B\u4F20\u5165\u56FE\u4E66\u540D 32 | book.title.length=\u56FE\u4E66\u540D\u4E0D\u80FD\u8D85\u8FC750\u5B57\u7B26 33 | book.author.not-empty=\u5FC5\u987B\u4F20\u5165\u56FE\u4E66\u4F5C\u8005 34 | book.author.length=\u56FE\u4E66\u4F5C\u8005\u540D\u4E0D\u80FD\u8D85\u8FC730\u5B57\u7B26 35 | book.summary.not-empty=\u5FC5\u987B\u4F20\u5165\u56FE\u4E66\u7EFC\u8FF0 36 | book.summary.length=\u56FE\u4E66\u7EFC\u8FF0\u4E0D\u80FD\u8D85\u8FC71000\u5B57\u7B26 37 | book.image.length=\u56FE\u4E66\u63D2\u56FE\u7684url\u957F\u5EA6\u5FC5\u987B\u57280~100\u4E4B\u95F4 38 | -------------------------------------------------------------------------------- /src/test/java/io/github/talelin/latticy/mapper/UserMapperTest.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import io.github.talelin.latticy.common.mybatis.LinPage; 5 | import io.github.talelin.latticy.model.GroupDO; 6 | import io.github.talelin.latticy.model.UserDO; 7 | import io.github.talelin.latticy.model.UserGroupDO; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.test.annotation.Rollback; 12 | import org.springframework.test.context.ActiveProfiles; 13 | import org.springframework.transaction.annotation.Transactional; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertTrue; 16 | 17 | 18 | @SpringBootTest 19 | @Transactional 20 | @Rollback 21 | @ActiveProfiles("test") 22 | class UserMapperTest { 23 | 24 | @Autowired 25 | private UserMapper userMapper; 26 | 27 | @Autowired 28 | private GroupMapper groupMapper; 29 | 30 | @Autowired 31 | private UserGroupMapper userGroupMapper; 32 | 33 | 34 | @Test 35 | void selectCountByUsername() { 36 | String email = "13129982604@qq.com"; 37 | String username = "pedro-test"; 38 | UserDO userDO = new UserDO(); 39 | userDO.setEmail(email); 40 | userDO.setUsername(username); 41 | userMapper.insert(userDO); 42 | int count = userMapper.selectCountByUsername(username); 43 | assertTrue(count > 0); 44 | } 45 | 46 | @Test 47 | void selectCountById() { 48 | String email = "13129982604@qq.com"; 49 | String username = "pedro-test"; 50 | UserDO userDO = new UserDO(); 51 | userDO.setEmail(email); 52 | userDO.setUsername(username); 53 | userMapper.insert(userDO); 54 | int count = userMapper.selectCountById(userDO.getId()); 55 | assertTrue(count > 0); 56 | } 57 | 58 | @Test 59 | void selectPageByGroupId() { 60 | String email = "13129982604@qq.com"; 61 | String username = "pedro-test"; 62 | UserDO userDO = new UserDO(); 63 | userDO.setEmail(email); 64 | userDO.setUsername(username); 65 | userMapper.insert(userDO); 66 | 67 | GroupDO group = GroupDO.builder().name("group").info("零零落落").build(); 68 | groupMapper.insert(group); 69 | 70 | userGroupMapper.insert(new UserGroupDO(userDO.getId(), group.getId())); 71 | 72 | LinPage page = new LinPage(0, 10); 73 | IPage iPage = userMapper.selectPageByGroupId(page, group.getId(), 99999); 74 | assertTrue(iPage.getTotal() > 0); 75 | boolean anyMatch = iPage.getRecords().stream().anyMatch(it -> it.getUsername().equals(username)); 76 | assertTrue(anyMatch); 77 | } 78 | } -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/extension/file/QiniuUploader.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.extension.file; 2 | 3 | import com.qiniu.common.QiniuException; 4 | import com.qiniu.http.Response; 5 | import com.qiniu.storage.Configuration; 6 | import com.qiniu.storage.Region; 7 | import com.qiniu.storage.UploadManager; 8 | import com.qiniu.util.Auth; 9 | import io.github.talelin.latticy.module.file.AbstractUploader; 10 | import io.github.talelin.latticy.module.file.FileProperties; 11 | import io.github.talelin.latticy.module.file.FileTypeEnum; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.beans.factory.annotation.Value; 15 | 16 | import java.io.ByteArrayInputStream; 17 | 18 | /** 19 | * 文件上传服务实现,上传到七牛云 20 | * 21 | * @author pedro@TaleLin 22 | * @author colorful@TaleLin 23 | */ 24 | @Slf4j 25 | public class QiniuUploader extends AbstractUploader { 26 | 27 | @Autowired 28 | private FileProperties fileProperties; 29 | 30 | @Value("${lin.file.qiniuyun.access-key}") 31 | private String accessKey; 32 | 33 | @Value("${lin.file.qiniuyun.secret-key}") 34 | private String secretKey; 35 | 36 | @Value("${lin.file.qiniuyun.bucket}") 37 | private String bucket; 38 | 39 | private UploadManager uploadManager; 40 | 41 | private String upToken; 42 | 43 | public void initUploadManager() { 44 | Configuration cfg = new Configuration(Region.region2()); 45 | uploadManager = new UploadManager(cfg); 46 | Auth auth = Auth.create(accessKey, secretKey); 47 | upToken = auth.uploadToken(bucket); 48 | } 49 | 50 | @Override 51 | protected FileProperties getFileProperties() { 52 | return fileProperties; 53 | } 54 | 55 | @Override 56 | protected String getStorePath(String newFilename) { 57 | return fileProperties.getDomain() + newFilename; 58 | } 59 | 60 | @Override 61 | protected String getFileType() { 62 | return FileTypeEnum.REMOTE.getValue(); 63 | } 64 | 65 | /** 66 | * 处理一个文件数据 67 | * 68 | * @param bytes 文件数据,比特流 69 | * @param newFilename 新文件名称 70 | * @return 处理是否成功,如果出现异常则返回 false,避免把失败的写入数据库 71 | */ 72 | @Override 73 | protected boolean handleOneFile(byte[] bytes, String newFilename) { 74 | initUploadManager(); 75 | ByteArrayInputStream byteInputStream = new ByteArrayInputStream(bytes); 76 | try { 77 | Response response = uploadManager.put(byteInputStream, newFilename, upToken, null, null); 78 | log.info(response.toString()); 79 | return response.isOK(); 80 | } catch (QiniuException ex) { 81 | Response r = ex.response; 82 | log.error("qiniuyun upload file error: {}", r.error); 83 | return false; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/resources/mapper/PermissionMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 34 | 35 | 53 | 54 | 67 | 68 | -------------------------------------------------------------------------------- /src/test/java/io/github/talelin/latticy/mapper/LogMapperTest.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import io.github.talelin.latticy.common.mybatis.LinPage; 5 | import io.github.talelin.latticy.model.LogDO; 6 | import org.junit.jupiter.api.BeforeAll; 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.TestInstance; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.test.annotation.Rollback; 12 | import org.springframework.test.context.ActiveProfiles; 13 | import org.springframework.transaction.annotation.Transactional; 14 | 15 | import java.util.Date; 16 | import java.util.List; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | import static org.junit.jupiter.api.Assertions.assertTrue; 20 | 21 | 22 | @SpringBootTest 23 | @Transactional 24 | @Rollback 25 | @ActiveProfiles("test") 26 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 27 | class LogMapperTest { 28 | 29 | @Autowired 30 | private LogMapper logMapper; 31 | 32 | private Date start = new Date(); 33 | private final String permission = "查看lin的信息"; 34 | private final String message = "就是个瓜皮"; 35 | private final String method = "GET"; 36 | private final String path = "/"; 37 | private final Integer statusCode = 200; 38 | private final Integer userId = 1; 39 | private final String username = "super"; 40 | 41 | @BeforeAll 42 | public void setUp() throws Exception { 43 | LogDO logDO = new LogDO(); 44 | logDO.setPermission(permission); 45 | logDO.setMessage(message); 46 | logDO.setMethod(method); 47 | logDO.setPath(path); 48 | logDO.setStatusCode(statusCode); 49 | logDO.setUserId(userId); 50 | logDO.setUsername(username); 51 | logDO.setCreateTime(start); 52 | long ll = start.getTime() - 500000; 53 | // start.setTime(ll); 54 | start = new Date(ll); 55 | logMapper.insert(logDO); 56 | } 57 | 58 | @Test 59 | void testFindLogsByUsernameAndRange() { 60 | Date now = new Date(); 61 | LinPage page = new LinPage<>(0, 10); 62 | IPage iPage = logMapper.findLogsByUsernameAndRange(page, username, start, now); 63 | List logs = iPage.getRecords(); 64 | assertTrue(logs.size() > 0); 65 | } 66 | 67 | @Test 68 | void testFindLogsByUsernameAndRange1() { 69 | long changed = start.getTime(); 70 | Date ch = new Date(changed - 1000); 71 | Date ch1 = new Date(changed - 2000); 72 | LinPage page = new LinPage<>(1, 10); 73 | IPage iPage = logMapper.findLogsByUsernameAndRange(page, username, ch1, ch); 74 | List logs = iPage.getRecords(); 75 | assertEquals(0, logs.size()); 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/listener/PermissionHandleListener.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.listener; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import io.github.talelin.autoconfigure.bean.MetaInfo; 5 | import io.github.talelin.autoconfigure.bean.PermissionMetaCollector; 6 | import io.github.talelin.latticy.model.PermissionDO; 7 | import io.github.talelin.latticy.service.PermissionService; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.context.ApplicationListener; 10 | import org.springframework.context.event.ContextRefreshedEvent; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * @author pedro@TaleLin 18 | * @author colorful@TaleLin 19 | * 权限监听器 20 | */ 21 | @Component 22 | public class PermissionHandleListener implements ApplicationListener { 23 | 24 | @Autowired 25 | private PermissionService permissionService; 26 | 27 | @Autowired 28 | private PermissionMetaCollector metaCollector; 29 | 30 | @Override 31 | public void onApplicationEvent(ContextRefreshedEvent event) { 32 | addNewPermissions(); 33 | removeUnusedPermissions(); 34 | } 35 | 36 | private void addNewPermissions() { 37 | metaCollector.getMetaMap().values().forEach(meta -> { 38 | String module = meta.getModule(); 39 | String permission = meta.getPermission(); 40 | createPermissionIfNotExist(permission, module); 41 | }); 42 | } 43 | 44 | private void removeUnusedPermissions() { 45 | List allPermissions = permissionService.list(); 46 | Map metaMap = metaCollector.getMetaMap(); 47 | for (PermissionDO permission : allPermissions) { 48 | boolean stayedInMeta = metaMap 49 | .values() 50 | .stream() 51 | .anyMatch(meta -> meta.getModule().equals(permission.getModule()) 52 | && meta.getPermission().equals(permission.getName())); 53 | if (!stayedInMeta) { 54 | permission.setMount(false); 55 | permissionService.updateById(permission); 56 | } 57 | } 58 | } 59 | 60 | private void createPermissionIfNotExist(String name, String module) { 61 | QueryWrapper wrapper = new QueryWrapper<>(); 62 | wrapper.lambda().eq(PermissionDO::getName, name).eq(PermissionDO::getModule, module); 63 | PermissionDO permission = permissionService.getOne(wrapper); 64 | if (permission == null) { 65 | permissionService.save(PermissionDO.builder().module(module).name(name).build()); 66 | } 67 | if (permission != null && !permission.getMount()) { 68 | permission.setMount(true); 69 | permissionService.updateById(permission); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/controller/v1/BookController.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.controller.v1; 2 | 3 | import io.github.talelin.autoconfigure.exception.NotFoundException; 4 | import io.github.talelin.core.annotation.GroupRequired; 5 | import io.github.talelin.core.annotation.PermissionMeta; 6 | import io.github.talelin.latticy.dto.book.CreateOrUpdateBookDTO; 7 | import io.github.talelin.latticy.model.BookDO; 8 | import io.github.talelin.latticy.service.BookService; 9 | import io.github.talelin.latticy.vo.CreatedVO; 10 | import io.github.talelin.latticy.vo.DeletedVO; 11 | import io.github.talelin.latticy.vo.UpdatedVO; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.validation.annotation.Validated; 14 | import org.springframework.web.bind.annotation.*; 15 | 16 | import javax.validation.constraints.Positive; 17 | import java.util.List; 18 | 19 | /** 20 | * 图书控制器 21 | * @author pedro@TaleLin 22 | * @author Juzi@TaleLin 23 | */ 24 | @RestController 25 | @RequestMapping("/v1/book") 26 | @Validated 27 | public class BookController { 28 | 29 | @Autowired 30 | private BookService bookService; 31 | 32 | @GetMapping("/{id}") 33 | public BookDO getBook(@PathVariable(value = "id") @Positive(message = "{id.positive}") Integer id) { 34 | BookDO book = bookService.getById(id); 35 | if (book == null) { 36 | throw new NotFoundException(10022); 37 | } 38 | return book; 39 | } 40 | 41 | @GetMapping("") 42 | public List getBooks() { 43 | return bookService.findAll(); 44 | } 45 | 46 | 47 | @GetMapping("/search") 48 | public List searchBook(@RequestParam(value = "q", required = false, defaultValue = "") String q) { 49 | return bookService.getBookByKeyword("%" + q + "%"); 50 | } 51 | 52 | 53 | @PostMapping("") 54 | public CreatedVO createBook(@RequestBody @Validated CreateOrUpdateBookDTO validator) { 55 | bookService.createBook(validator); 56 | return new CreatedVO(12); 57 | } 58 | 59 | 60 | @PutMapping("/{id}") 61 | public UpdatedVO updateBook(@PathVariable("id") @Positive(message = "{id.positive}") Integer id, @RequestBody @Validated CreateOrUpdateBookDTO validator) { 62 | BookDO book = bookService.getById(id); 63 | if (book == null) { 64 | throw new NotFoundException(10022); 65 | } 66 | bookService.updateBook(book, validator); 67 | return new UpdatedVO(13); 68 | } 69 | 70 | 71 | @DeleteMapping("/{id}") 72 | @GroupRequired 73 | @PermissionMeta(value = "删除图书", module = "图书") 74 | public DeletedVO deleteBook(@PathVariable("id") @Positive(message = "{id.positive}") Integer id) { 75 | BookDO book = bookService.getById(id); 76 | if (book == null) { 77 | throw new NotFoundException(10022); 78 | } 79 | bookService.deleteById(book.getId()); 80 | return new DeletedVO(14); 81 | } 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/service/GroupService.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import io.github.talelin.latticy.bo.GroupPermissionBO; 6 | import io.github.talelin.latticy.common.enumeration.GroupLevelEnum; 7 | import io.github.talelin.latticy.model.GroupDO; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author pedro@TaleLin 13 | * @author Juzi@TaleLin 14 | * 分组服务接口 15 | */ 16 | public interface GroupService extends IService { 17 | 18 | /** 19 | * 获得用户的所有分组 20 | * 21 | * @param userId 用户id 22 | * @return 所有分组 23 | */ 24 | List getUserGroupsByUserId(Integer userId); 25 | 26 | /** 27 | * 获得用户的所有分组id 28 | * 29 | * @param userId 用户id 30 | * @return 所有分组id 31 | */ 32 | List getUserGroupIdsByUserId(Integer userId); 33 | 34 | /** 35 | * 分页获取分组数据 36 | * 37 | * @param count 分页数量 38 | * @param page 那一页 39 | * @return 分组页 40 | */ 41 | IPage getGroupPage(int page, int count); 42 | 43 | /** 44 | * 通过id检查分组是否存在 45 | * 46 | * @param id 分组id 47 | * @return 是否存在 48 | */ 49 | boolean checkGroupExistById(Integer id); 50 | 51 | /** 52 | * 获得分组及其权限 53 | * 54 | * @param id 分组id 55 | * @return 分组及权限 56 | */ 57 | GroupPermissionBO getGroupAndPermissions(Integer id); 58 | 59 | /** 60 | * 通过名称检查分组是否存在 61 | * 62 | * @param name 分组名 63 | * @return 是否存在 64 | */ 65 | boolean checkGroupExistByName(String name); 66 | 67 | /** 68 | * 检查该用户是否在root分组中 69 | * 70 | * @param userId 用户id 71 | * @return true表示在 72 | */ 73 | boolean checkIsRootByUserId(Integer userId); 74 | 75 | /** 76 | * 删除用户与分组直接的关联 77 | * 78 | * @param userId 用户id 79 | * @param deleteIds 分组id 80 | */ 81 | boolean deleteUserGroupRelations(Integer userId, List deleteIds); 82 | 83 | /** 84 | * 添加用户与分组直接的关联 85 | * 86 | * @param userId 用户id 87 | * @param addIds 分组id 88 | */ 89 | boolean addUserGroupRelations(Integer userId, List addIds); 90 | 91 | /** 92 | * 获得分组下所有用户的id 93 | * 94 | * @param id 分组id 95 | * @return 用户id 96 | */ 97 | List getGroupUserIds(Integer id); 98 | 99 | /** 100 | * 通过分组级别获取超级管理员分组或游客分组 101 | * 102 | * @param level GroupLevelEnum 枚举类 103 | * @return 用户组 104 | */ 105 | GroupDO getParticularGroupByLevel(GroupLevelEnum level); 106 | 107 | /** 108 | * 通过分组级别获取超级管理员分组或游客分组的id 109 | * 110 | * @param level GroupLevelEnum 枚举类 111 | * @return 用户组id 112 | */ 113 | Integer getParticularGroupIdByLevel(GroupLevelEnum level); 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/test/resources/mpg/templates/controller.java.ftl: -------------------------------------------------------------------------------- 1 | package ${package.Controller}; 2 | 3 | 4 | import org.springframework.web.bind.annotation.PostMapping; 5 | import org.springframework.web.bind.annotation.PutMapping; 6 | import org.springframework.web.bind.annotation.DeleteMapping; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.RequestParam; 11 | import ${package.Entity}.${entity}; 12 | import io.github.talelin.latticy.vo.CreatedVO; 13 | import io.github.talelin.latticy.vo.DeletedVO; 14 | import io.github.talelin.latticy.vo.PageResponseVO; 15 | import io.github.talelin.latticy.vo.UpdatedVO; 16 | 17 | import javax.validation.constraints.Min; 18 | import javax.validation.constraints.Max; 19 | import javax.validation.constraints.Positive; 20 | 21 | <#if restControllerStyle> 22 | import org.springframework.web.bind.annotation.RestController; 23 | <#else> 24 | import org.springframework.stereotype.Controller; 25 | 26 | <#if superControllerClassPackage??> 27 | import ${superControllerClassPackage}; 28 | 29 | 30 | /** 31 | <#if table.comment != ""> 32 | * ${table.comment!}前端控制器 33 | * 34 | 35 | * @author ${author} 36 | * @since ${date} 37 | */ 38 | <#if restControllerStyle> 39 | @RestController 40 | <#else> 41 | @Controller 42 | 43 | @RequestMapping("/${package.Controller?split(".")?last}<#if package.ModuleName?? && package.ModuleName != "">/${package.ModuleName}/<#if controllerMappingHyphenStyle??>${controllerMappingHyphen?replace("-do", "")}<#else>${table.entityPath?replace("DO", "")}") 44 | <#if kotlin> 45 | class ${table.controllerName}<#if superControllerClass??> : ${superControllerClass}() 46 | <#else> 47 | <#if superControllerClass??> 48 | public class ${table.controllerName} extends ${superControllerClass} { 49 | <#else> 50 | public class ${table.controllerName} { 51 | 52 | 53 | @PostMapping("") 54 | public CreatedVO create() { 55 | return new CreatedVO(); 56 | } 57 | 58 | @PutMapping("/{id}") 59 | public UpdatedVO update(@PathVariable @Positive(message = "{id.positive}") Integer id) { 60 | return new UpdatedVO(); 61 | } 62 | 63 | @DeleteMapping("/{id}") 64 | public DeletedVO delete(@PathVariable @Positive(message = "{id.positive}") Integer id) { 65 | return new DeletedVO(); 66 | } 67 | 68 | @GetMapping("/{id}") 69 | public ${entity} get(@PathVariable(value = "id") @Positive(message = "{id.positive}") Integer id) { 70 | return null; 71 | } 72 | 73 | @GetMapping("/page") 74 | public PageResponseVO<${entity}> page( 75 | @RequestParam(name = "page", required = false, defaultValue = "0") 76 | @Min(value = 0, message = "{page.number.min}") Integer page, 77 | @RequestParam(name = "count", required = false, defaultValue = "10") 78 | @Min(value = 1, message = "{page.count.min}") 79 | @Max(value = 30, message = "{page.count.max}") Integer count 80 | ) { 81 | return null; 82 | } 83 | 84 | } 85 | 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 |
5 | Lin-CMS-Spring-boot 6 |

7 | 8 |

一个简单易用的CMS后端项目 | Lin-CMS-Spring-boot

9 |

10 | 11 | 12 | spring boot 13 | 14 | 15 | 16 | mybatis-plus 17 | 18 | 19 | 20 | LISENCE 21 | 22 | 23 |

24 | 25 |
26 | Lin-CMS 是林间有风团队经过大量项目实践所提炼出的一套内容管理系统框架
27 | Lin-CMS 可以有效的帮助开发者提高 CMS 的开发效率。 28 |
29 | 30 |

31 | 预览 |  32 | 简介 |  33 | 联系和交流 34 |

35 | 36 | ## 预览 37 | 38 | ### 线上 demo 39 | 40 | [http://face.cms.talelin.com/](http://face.cms.talelin.com/) 41 | 42 | ### 文档地址 43 | 44 | [http://doc.cms.talelin.com/](http://doc.cms.talelin.com/) 45 | 46 | ## 简介 47 | 48 | ### 什么是Lin CMS 49 | 50 | Lin-CMS 是林间有风团队经过大量项目实践所提炼出的一套**内容管理系统框 51 | 架**。Lin-CMS 可以有效的帮助开发者提高 CMS 的开发效率。 52 | 53 | 本项目是 Lin CMS 后端的 java spring-boot 实现,需要前端?请访 54 | 问[前端仓库](https://github.com/TaleLin/lin-cms-vue)。 55 | 56 | ### Lin CMS 特点 57 | 58 | Lin CMS 的构筑思想是有其自身特点的。下面我们阐述一些 Lin 的主要特点。 59 | 60 | #### Lin CMS 是一个前后端分离的 CMS 解决方案 61 | 62 | Lin 既提供后台的支撑,也有一套对应的前端系统,你也可以选择不同的后端实现, 63 | 如 koa 和 flask。 64 | 65 | #### 框架本身已内置了 CMS 常用的功能 66 | 67 | Lin 已经内置了 CMS 中最为常见的需求:用户管理、权限管理、日志系统等。开发者只需 68 | 要集中精力开发自己的 CMS 业务即可 69 | 70 | #### Lin CMS 本身也是一套开发规范 71 | 72 | Lin CMS 除了内置常见的功能外,还提供了一套开发规范与工具类。 73 | Lin CMS 只需要开发者关注自己的业务开发,它已经内置了很多机制帮助开发者快速开发自己的业务。 74 | 75 | #### 扩展灵活 76 | 77 | Lin CMS 支持 `extension` 来便捷地增强你的业务。 78 | 79 | #### 前端支持 80 | 81 | Lin CMS 也有自己的前端实现,强强联合为你助力。 82 | 83 | #### 完善的文档 84 | 85 | Lin CMS 提供大量的文档来帮助开发者使用 86 | 87 | 88 | ## 联系和交流 89 | ![](https://img.juzibiji.top/20200807155013.png) 90 | ## 注意事项 91 | 92 | - Lin CMS 需要一定的基础,至少你得有一定的 java 基础和数据库基础,并且比较熟悉spring-boot和mybatis, 93 | 当然如果你是个 java 程序员,这些肯定都不在话下。 94 | 95 | 96 | - Lin CMS 基于 spring boot ,因此也采取了 spring boot 的 starter (启动器)机制,我们也有自己的 97 | starter,见 [lin-cms-java-core](https://github.com/TaleLin/lin-cms-java-core.git)。 98 | 99 | ## 贡献代码 100 | 101 | 我们的代码基于 develop 分支开发,欢迎提交 Pull Request 进行代码贡献。 102 | 103 | 在提交 Pull Request 之前,请详细阅读我们的[开发规范](https://doc.cms.talelin.com/specification/),否则可能因为 Commit 信息不规范等原因被关闭 Pull Request。 104 | 105 | 106 | 107 | ## 开源协议 108 | 109 | [MIT](LICENSE) © 2021 林间有风 110 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/module/message/WsHandler.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.module.message; 2 | 3 | import org.springframework.web.socket.TextMessage; 4 | import org.springframework.web.socket.WebSocketSession; 5 | 6 | import java.io.IOException; 7 | import java.util.Set; 8 | 9 | /** 10 | * @author pedro@TaleLin 11 | * @author Juzi@TaleLin 12 | * websocket 接口 13 | */ 14 | public interface WsHandler { 15 | 16 | /** 17 | * 会话开始回调 18 | * 19 | * @param session 会话 20 | */ 21 | void handleOpen(WebSocketSession session); 22 | 23 | /** 24 | * 会话结束回调 25 | * 26 | * @param session 会话 27 | */ 28 | void handleClose(WebSocketSession session); 29 | 30 | /** 31 | * 处理消息 32 | * 33 | * @param session 会话 34 | * @param message 接收的消息 35 | */ 36 | void handleMessage(WebSocketSession session, String message); 37 | 38 | /** 39 | * 发送消息 40 | * 41 | * @param session 当前会话 42 | * @param message 要发送的消息 43 | * @throws IOException 发送io异常 44 | */ 45 | void sendMessage(WebSocketSession session, String message) throws IOException; 46 | 47 | /** 48 | * 发送消息 49 | * 50 | * @param userId 用户id 51 | * @param message 要发送的消息 52 | * @throws IOException 发送io异常 53 | */ 54 | void sendMessage(Integer userId, TextMessage message) throws IOException; 55 | 56 | /** 57 | * 发送消息 58 | * 59 | * @param userId 用户id 60 | * @param message 要发送的消息 61 | * @throws IOException 发送io异常 62 | */ 63 | void sendMessage(Integer userId, String message) throws IOException; 64 | 65 | /** 66 | * 发送消息 67 | * 68 | * @param session 当前会话 69 | * @param message 要发送的消息 70 | * @throws IOException 发送io异常 71 | */ 72 | void sendMessage(WebSocketSession session, TextMessage message) throws IOException; 73 | 74 | /** 75 | * 广播 76 | * 77 | * @param message 字符串消息 78 | * @throws IOException 异常 79 | */ 80 | void broadCast(String message) throws IOException; 81 | 82 | /** 83 | * 对某个分组广播 84 | * 85 | * @param groupId 分组id 86 | * @param message 消息 87 | * @throws IOException 异常 88 | */ 89 | void broadCastToGroup(Integer groupId, String message) throws IOException; 90 | 91 | /** 92 | * 广播 93 | * 94 | * @param message 文本消息 95 | * @throws IOException 异常 96 | */ 97 | void broadCast(TextMessage message) throws IOException; 98 | 99 | /** 100 | * 对某个分组广播 101 | * 102 | * @param groupId 分组id 103 | * @param message 文本消息 104 | * @throws IOException 异常 105 | */ 106 | void broadCastToGroup(Integer groupId, TextMessage message) throws IOException; 107 | 108 | /** 109 | * 处理会话异常 110 | * 111 | * @param session 会话 112 | * @param error 异常 113 | */ 114 | void handleError(WebSocketSession session, Throwable error); 115 | 116 | /** 117 | * 获得所有的 websocket 会话 118 | * 119 | * @return 所有 websocket 会话 120 | */ 121 | Set getSessions(); 122 | 123 | /** 124 | * 得到当前连接数 125 | * 126 | * @return 连接数 127 | */ 128 | int getConnectionCount(); 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/interceptor/LoggerImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.interceptor; 2 | 3 | import io.github.talelin.autoconfigure.interfaces.LoggerResolver; 4 | import io.github.talelin.core.annotation.Logger; 5 | import io.github.talelin.core.annotation.PermissionMeta; 6 | import io.github.talelin.core.util.BeanUtil; 7 | import io.github.talelin.latticy.common.LocalUser; 8 | import io.github.talelin.latticy.model.UserDO; 9 | import io.github.talelin.latticy.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 | /** 20 | * @author pedro@TaleLin 21 | * @author Juzi@TaleLin 22 | * @author colorful@TaleLin 23 | * 行为日志实现类 24 | */ 25 | @Slf4j 26 | @Component 27 | public class LoggerImpl implements LoggerResolver { 28 | 29 | @Autowired 30 | private LogService logService; 31 | 32 | /** 33 | * 日志格式匹配正则 34 | */ 35 | private static final Pattern LOG_PATTERN = Pattern.compile("(?<=\\{)[^}]*(?=})"); 36 | 37 | @Override 38 | public void handle(PermissionMeta meta, Logger logger, HttpServletRequest request, HttpServletResponse response) { 39 | String template = logger.template(); 40 | UserDO user = LocalUser.getLocalUser(); 41 | template = this.parseTemplate(template, user, request, response); 42 | String permission = ""; 43 | if (meta != null) { 44 | permission = meta.value(); 45 | } 46 | Integer userId = user.getId(); 47 | String username = user.getUsername(); 48 | String method = request.getMethod(); 49 | String path = request.getServletPath(); 50 | Integer status = response.getStatus(); 51 | logService.createLog(template, permission, userId, username, method, path, status); 52 | } 53 | 54 | private String parseTemplate(String template, UserDO user, HttpServletRequest request, HttpServletResponse response) { 55 | // 调用 get 方法 56 | Matcher m = LOG_PATTERN.matcher(template); 57 | while (m.find()) { 58 | String group = m.group(); 59 | String property = this.extractProperty(group, user, request, response); 60 | template = template.replace("{" + group + "}", property); 61 | } 62 | return template; 63 | } 64 | 65 | private String extractProperty(String item, UserDO user, HttpServletRequest request, HttpServletResponse response) { 66 | int i = item.lastIndexOf('.'); 67 | String obj = item.substring(0, i); 68 | String prop = item.substring(i + 1); 69 | switch (obj) { 70 | case "user": 71 | if (user == null) { 72 | return ""; 73 | } 74 | return BeanUtil.getValueByPropName(user, prop); 75 | case "request": 76 | return BeanUtil.getValueByPropName(request, prop); 77 | case "response": 78 | return BeanUtil.getValueByPropName(response, prop); 79 | default: 80 | return ""; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/util/ResponseUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.util; 2 | 3 | import io.github.talelin.autoconfigure.bean.Code; 4 | import io.github.talelin.autoconfigure.util.RequestUtil; 5 | import io.github.talelin.latticy.vo.UnifyResponseVO; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.web.context.request.RequestContextHolder; 8 | import org.springframework.web.context.request.ServletRequestAttributes; 9 | 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | 13 | /** 14 | * 响应结果生成工具类 15 | * @author pedro@TaleLin 16 | */ 17 | @SuppressWarnings("unchecked") 18 | @Slf4j 19 | public class ResponseUtil { 20 | 21 | private ResponseUtil() { 22 | throw new IllegalStateException("Utility class"); 23 | } 24 | 25 | /** 26 | * 获得当前响应 27 | * 28 | * @return 响应 29 | */ 30 | public static HttpServletResponse getResponse() { 31 | return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); 32 | } 33 | 34 | public static void setCurrentResponseHttpStatus(int httpStatus) { 35 | getResponse().setStatus(httpStatus); 36 | } 37 | 38 | public static UnifyResponseVO generateCreatedResponse(int code) { 39 | return (UnifyResponseVO) UnifyResponseVO.builder() 40 | .message(Code.CREATED.getDescription()) 41 | .code(code) 42 | .request(RequestUtil.getSimpleRequest()) 43 | .build(); 44 | } 45 | 46 | public static UnifyResponseVO generateCreatedResponse(int code, T data) { 47 | return (UnifyResponseVO) UnifyResponseVO.builder() 48 | .message(data) 49 | .code(code) 50 | .request(RequestUtil.getSimpleRequest()) 51 | .build(); 52 | } 53 | 54 | public static UnifyResponseVO generateDeletedResponse(int code) { 55 | return (UnifyResponseVO) UnifyResponseVO.builder() 56 | .message(Code.SUCCESS.getDescription()) 57 | .code(code) 58 | .request(RequestUtil.getSimpleRequest()) 59 | .build(); 60 | } 61 | 62 | public static UnifyResponseVO generateDeletedResponse(int code, T data) { 63 | return (UnifyResponseVO) UnifyResponseVO.builder() 64 | .message(data) 65 | .code(code) 66 | .request(RequestUtil.getSimpleRequest()) 67 | .build(); 68 | } 69 | 70 | public static UnifyResponseVO generateUpdatedResponse(int code) { 71 | return (UnifyResponseVO) UnifyResponseVO.builder() 72 | .message(Code.SUCCESS.getDescription()) 73 | .code(code) 74 | .request(RequestUtil.getSimpleRequest()) 75 | .build(); 76 | } 77 | 78 | public static UnifyResponseVO generateUpdatedResponse(int code, T data) { 79 | return (UnifyResponseVO) UnifyResponseVO.builder() 80 | .message(data) 81 | .code(code) 82 | .request(RequestUtil.getSimpleRequest()) 83 | .build(); 84 | } 85 | 86 | public static UnifyResponseVO generateUnifyResponse(int code) { 87 | return (UnifyResponseVO) UnifyResponseVO.builder() 88 | .code(code) 89 | .request(RequestUtil.getSimpleRequest()) 90 | .build(); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/configuration/CommonConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.configuration; 2 | 3 | import com.baomidou.mybatisplus.annotation.DbType; 4 | import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer; 5 | import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; 6 | import com.baomidou.mybatisplus.core.injector.ISqlInjector; 7 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 8 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; 9 | import com.fasterxml.jackson.databind.PropertyNamingStrategies; 10 | import io.github.talelin.autoconfigure.bean.PermissionMetaCollector; 11 | import io.github.talelin.latticy.common.interceptor.RequestLogInterceptor; 12 | import io.github.talelin.latticy.module.log.MDCAccessServletFilter; 13 | import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; 14 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | 18 | /** 19 | * @author pedro@TaleLin 20 | * @author colorful@TaleLin 21 | * 22 | * 公共配置 23 | */ 24 | @Configuration(proxyBeanMethods = false) 25 | public class CommonConfiguration { 26 | 27 | @Bean 28 | public RequestLogInterceptor requestLogInterceptor() { 29 | return new RequestLogInterceptor(); 30 | } 31 | 32 | /** 33 | * 新的分页插件,一缓和二缓遵循mybatis的规则 34 | */ 35 | @Bean 36 | public MybatisPlusInterceptor mybatisPlusInterceptor() { 37 | MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 38 | interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); 39 | return interceptor; 40 | } 41 | 42 | @Bean 43 | @SuppressWarnings("deprecation") 44 | public ConfigurationCustomizer configurationCustomizer() { 45 | return configuration -> configuration.setUseDeprecatedExecutor(false); 46 | } 47 | 48 | @Bean 49 | public ISqlInjector sqlInjector() { 50 | return new DefaultSqlInjector(); 51 | } 52 | 53 | /** 54 | * 记录每个被 @PermissionMeta 记录的信息,在beans的后置调用 55 | * 56 | * @return PermissionMetaCollector 57 | */ 58 | @Bean 59 | public PermissionMetaCollector postProcessBeans() { 60 | return new PermissionMetaCollector(); 61 | } 62 | 63 | 64 | /** 65 | * 接口中,自动转换的有:驼峰转换为下划线,空值输出null 66 | */ 67 | @Bean 68 | public Jackson2ObjectMapperBuilderCustomizer customJackson() { 69 | return jacksonObjectMapperBuilder -> { 70 | jacksonObjectMapperBuilder.failOnUnknownProperties(false); 71 | jacksonObjectMapperBuilder.propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); 72 | }; 73 | } 74 | 75 | /** 76 | * 用于将 request 相关信息(如请求 url)放入 MDC 中供日志使用 77 | * 78 | * @return Logback 的 MDCInsertingServletFilter 79 | */ 80 | @Bean 81 | public FilterRegistrationBean mdcInsertingServletFilter() { 82 | FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(); 83 | MDCAccessServletFilter mdcAccessServletFilter = new MDCAccessServletFilter(); 84 | filterRegistrationBean.setFilter(mdcAccessServletFilter); 85 | filterRegistrationBean.setName("mdc-access-servlet-filter"); 86 | return filterRegistrationBean; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/io/github/talelin/latticy/mapper/GroupMapperTest.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.mapper; 2 | 3 | import io.github.talelin.latticy.model.GroupDO; 4 | import io.github.talelin.latticy.model.UserDO; 5 | import io.github.talelin.latticy.model.UserGroupDO; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.annotation.Rollback; 10 | import org.springframework.test.context.ActiveProfiles; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | import java.util.List; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertTrue; 16 | 17 | 18 | @SpringBootTest 19 | @Transactional 20 | @Rollback 21 | @ActiveProfiles("test") 22 | public class GroupMapperTest { 23 | 24 | @Autowired 25 | private GroupMapper groupMapper; 26 | 27 | @Autowired 28 | private UserMapper userMapper; 29 | 30 | @Autowired 31 | private UserGroupMapper userGroupMapper; 32 | 33 | @Test 34 | public void selectGroupsByUserId() { 35 | String email = "13129982604@qq.com"; 36 | String username = "pedro-test"; 37 | UserDO user = new UserDO(); 38 | user.setEmail(email); 39 | user.setUsername(username); 40 | userMapper.insert(user); 41 | 42 | GroupDO group = GroupDO.builder().name("group").info("零零落落").build(); 43 | groupMapper.insert(group); 44 | 45 | userGroupMapper.insert(new UserGroupDO(user.getId(), group.getId())); 46 | 47 | List groups = groupMapper.selectGroupsByUserId(user.getId()); 48 | boolean anyMatch = groups.stream().anyMatch(it -> it.getName().equals("group")); 49 | assertTrue(anyMatch); 50 | } 51 | 52 | 53 | @Test 54 | public void selectUserGroupIDs() { 55 | String email = "13129982604@qq.com"; 56 | String username = "pedro-test"; 57 | UserDO user = new UserDO(); 58 | user.setEmail(email); 59 | user.setUsername(username); 60 | userMapper.insert(user); 61 | 62 | GroupDO group = GroupDO.builder().name("group").info("零零落落").build(); 63 | groupMapper.insert(group); 64 | 65 | userGroupMapper.insert(new UserGroupDO(user.getId(), group.getId())); 66 | 67 | List groupIds = groupMapper.selectUserGroupIds(user.getId()); 68 | boolean anyMatch = groupIds.stream().anyMatch(it -> it.equals(group.getId())); 69 | assertTrue(anyMatch); 70 | } 71 | 72 | 73 | @Test 74 | public void selectCountById() { 75 | GroupDO group = GroupDO.builder().name("group").info("零零落落").build(); 76 | groupMapper.insert(group); 77 | int count = groupMapper.selectCountById(group.getId()); 78 | assertTrue(count > 0); 79 | } 80 | 81 | @Test 82 | public void selectCountUserByUserIdAndGroupName() { 83 | String email = "13129982604@qq.com"; 84 | String username = "pedro-test"; 85 | UserDO user = new UserDO(); 86 | user.setEmail(email); 87 | user.setUsername(username); 88 | userMapper.insert(user); 89 | 90 | GroupDO group = GroupDO.builder().name("group").info("零零落落").build(); 91 | groupMapper.insert(group); 92 | 93 | userGroupMapper.insert(new UserGroupDO(user.getId(), group.getId())); 94 | 95 | int count = groupMapper.selectCountUserByUserIdAndGroupName(user.getId(), "group"); 96 | assertTrue(count > 0); 97 | } 98 | } -------------------------------------------------------------------------------- /src/test/java/io/github/talelin/latticy/service/impl/LogServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.core.metadata.IPage; 5 | import io.github.talelin.latticy.mapper.LogMapper; 6 | import io.github.talelin.latticy.model.LogDO; 7 | import io.github.talelin.latticy.service.LogService; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.test.annotation.Rollback; 12 | import org.springframework.test.context.ActiveProfiles; 13 | import org.springframework.transaction.annotation.Transactional; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | import static org.junit.jupiter.api.Assertions.assertTrue; 17 | 18 | 19 | @SpringBootTest 20 | @Transactional 21 | @Rollback 22 | @ActiveProfiles("test") 23 | public class LogServiceImplTest { 24 | 25 | @Autowired 26 | private LogService logService; 27 | 28 | @Autowired 29 | private LogMapper logMapper; 30 | 31 | @Test 32 | public void getLogs() { 33 | String message = "put your face to the light!"; 34 | String authority = "查看lin的信息"; 35 | Integer userId = 100; 36 | String userName = "pedro"; 37 | String method = "GET"; 38 | String path = "/"; 39 | Integer status = 200; 40 | logService.createLog(message, authority, userId, userName, method, path, status); 41 | 42 | IPage iPage = logService.getLogPage(0, 10, null, null, null); 43 | assertTrue(iPage.getSize() > 0); 44 | } 45 | 46 | @Test 47 | public void searchLogs() { 48 | String message = "put your face to the light!"; 49 | String authority = "查看lin的信息"; 50 | Integer userId = 100; 51 | String userName = "pedro"; 52 | String method = "GET"; 53 | String path = "/"; 54 | Integer status = 200; 55 | logService.createLog(message, authority, userId, userName, method, path, status); 56 | 57 | IPage iPage = logService.searchLogPage(0, 10, null, "put", null, null); 58 | assertTrue(iPage.getSize() > 0); 59 | } 60 | 61 | @Test 62 | public void getUserNames() { 63 | String message = "put your face to the light!"; 64 | String authority = "查看lin的信息"; 65 | Integer userId = 100; 66 | String userName = "pedro"; 67 | String method = "GET"; 68 | String path = "/"; 69 | Integer status = 200; 70 | logService.createLog(message, authority, userId, userName, method, path, status); 71 | 72 | IPage iPage = logService.getUserNamePage(0, 10); 73 | assertTrue(iPage.getRecords().size() > 0); 74 | } 75 | 76 | @Test 77 | public void createOneLog() { 78 | String message = "put your face to the light!"; 79 | String permission = "查看lin的信息"; 80 | Integer userId = 100; 81 | String userName = "pedro"; 82 | String method = "GET"; 83 | String path = "/"; 84 | Integer status = 200; 85 | logService.createLog(message, permission, userId, userName, method, path, status); 86 | 87 | QueryWrapper condition = new QueryWrapper<>(); 88 | condition.eq("message", message); 89 | LogDO logDO = logMapper.selectOne(condition); 90 | 91 | assertEquals(logDO.getPermission(), permission); 92 | } 93 | } -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/service/impl/FileServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import io.github.talelin.latticy.bo.FileBO; 5 | import io.github.talelin.latticy.mapper.FileMapper; 6 | import io.github.talelin.latticy.model.FileDO; 7 | import io.github.talelin.latticy.module.file.*; 8 | import io.github.talelin.latticy.service.FileService; 9 | import org.springframework.beans.BeanUtils; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.util.MultiValueMap; 13 | import org.springframework.web.multipart.MultipartFile; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | /** 19 | * @author pedro@TaleLin 20 | * @author Juzi@TaleLin 21 | * @author colorful@TaleLin 22 | * 文件服务接口实现类 23 | */ 24 | @Service 25 | public class FileServiceImpl extends ServiceImpl implements FileService { 26 | 27 | @Autowired 28 | private Uploader uploader; 29 | 30 | /** 31 | * 文件上传配置信息 32 | */ 33 | @Autowired 34 | private FileProperties fileProperties; 35 | 36 | /** 37 | * 为什么不做批量插入 38 | * 1. 文件上传的数量一般不多,3个左右 39 | * 2. 批量插入不能得到数据的id字段,不利于直接返回数据 40 | * 3. 批量插入也仅仅只是一条sql语句的事情,如果真的需要,可以自行尝试一下 41 | */ 42 | @Override 43 | public List upload(MultiValueMap fileMap) { 44 | List res = new ArrayList<>(); 45 | 46 | uploader.upload(fileMap, new UploadHandler() { 47 | @Override 48 | public boolean preHandle(File file) { 49 | FileDO found = baseMapper.selectByMd5(file.getMd5()); 50 | // 数据库中不存在,存储操作放在上传到本地或云上之后 51 | if (found == null) { 52 | return true; 53 | } 54 | // 已存在,则直接转化返回 55 | res.add(transformDoToBo(found, file.getKey())); 56 | return false; 57 | } 58 | 59 | @Override 60 | public void afterHandle(File file) { 61 | // 保存到数据库 62 | FileDO fileDO = new FileDO(); 63 | BeanUtils.copyProperties(file, fileDO); 64 | getBaseMapper().insert(fileDO); 65 | res.add(transformDoToBo(fileDO, file.getKey())); 66 | } 67 | }); 68 | return res; 69 | } 70 | 71 | @Override 72 | public boolean checkFileExistByMd5(String md5) { 73 | return this.getBaseMapper().selectCountByMd5(md5) > 0; 74 | } 75 | 76 | private FileBO transformDoToBo(FileDO file, String key) { 77 | FileBO bo = new FileBO(); 78 | BeanUtils.copyProperties(file, bo); 79 | if (file.getType().equals(FileTypeEnum.LOCAL.getValue())) { 80 | String s = fileProperties.getServePath().split("/")[0]; 81 | 82 | // replaceAll 是将 windows 平台下的 \ 替换为 / 83 | if(System.getProperties().getProperty("os.name").toUpperCase().contains("WINDOWS")){ 84 | bo.setUrl(fileProperties.getDomain() + s + "/" + file.getPath().replaceAll("\\\\","/")); 85 | } else { 86 | bo.setUrl(fileProperties.getDomain() + s + "/" + file.getPath()); 87 | } 88 | } else { 89 | bo.setUrl(file.getPath()); 90 | } 91 | bo.setKey(key); 92 | return bo; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/io/github/talelin/latticy/service/impl/BookServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.service.impl; 2 | 3 | import io.github.talelin.latticy.dto.book.CreateOrUpdateBookDTO; 4 | import io.github.talelin.latticy.mapper.BookMapper; 5 | import io.github.talelin.latticy.model.BookDO; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.annotation.Rollback; 10 | import org.springframework.test.context.ActiveProfiles; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | import java.util.List; 14 | 15 | import static org.junit.jupiter.api.Assertions.*; 16 | 17 | 18 | @SpringBootTest 19 | @Transactional 20 | @Rollback 21 | @ActiveProfiles("test") 22 | public class BookServiceImplTest { 23 | 24 | @Autowired 25 | private BookServiceImpl bookService; 26 | 27 | @Autowired 28 | private BookMapper bookMapper; 29 | 30 | private final String title = "千里之外"; 31 | private final String author = "pedro"; 32 | private final String image = "千里之外.png"; 33 | private final String summary = "千里之外,是周杰伦和费玉清一起发售的歌曲"; 34 | 35 | @Test 36 | public void createBook() { 37 | 38 | CreateOrUpdateBookDTO validator = new CreateOrUpdateBookDTO(); 39 | validator.setAuthor(author); 40 | validator.setImage(image); 41 | validator.setSummary(summary); 42 | validator.setTitle(title); 43 | bookService.createBook(validator); 44 | 45 | List books = bookMapper.selectByTitle(title); 46 | boolean anyMatch = books.stream().anyMatch(bo -> bo.getTitle().equals(title) && bo.getAuthor().equals(author)); 47 | assertTrue(anyMatch); 48 | } 49 | 50 | @Test 51 | public void getBookByKeyword() { 52 | BookDO bookDO = new BookDO(); 53 | bookDO.setTitle(title); 54 | bookDO.setAuthor(author); 55 | bookDO.setImage(image); 56 | bookDO.setSummary(summary); 57 | bookMapper.insert(bookDO); 58 | 59 | List books = bookService.getBookByKeyword("%千里%"); 60 | boolean anyMatch = books.stream().anyMatch(bo -> bo.getTitle().equals(title) && bo.getAuthor().equals(author)); 61 | assertTrue(anyMatch); 62 | } 63 | 64 | @Test 65 | public void updateBook() { 66 | BookDO bookDO = new BookDO(); 67 | bookDO.setTitle(title); 68 | bookDO.setAuthor(author); 69 | bookDO.setImage(image); 70 | bookDO.setSummary(summary); 71 | bookMapper.insert(bookDO); 72 | 73 | String newTitle = "tttttttt"; 74 | 75 | CreateOrUpdateBookDTO validator = new CreateOrUpdateBookDTO(); 76 | validator.setAuthor(author); 77 | validator.setImage(image); 78 | validator.setSummary(summary); 79 | validator.setTitle(newTitle); 80 | 81 | BookDO found = bookMapper.selectById(bookDO.getId()); 82 | bookService.updateBook(found, validator); 83 | 84 | BookDO found1 = bookMapper.selectById(bookDO.getId()); 85 | assertEquals(found1.getTitle(), newTitle); 86 | } 87 | 88 | @Test 89 | public void deleteById() { 90 | BookDO bookDO = new BookDO(); 91 | bookDO.setTitle(title); 92 | bookDO.setAuthor(author); 93 | bookDO.setImage(image); 94 | bookDO.setSummary(summary); 95 | bookMapper.insert(bookDO); 96 | 97 | bookService.deleteById(bookDO.getId()); 98 | BookDO hit = bookService.getById(bookDO.getId()); 99 | assertNull(hit); 100 | } 101 | } -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/service/AdminService.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import io.github.talelin.latticy.bo.GroupPermissionBO; 5 | import io.github.talelin.latticy.dto.admin.DispatchPermissionDTO; 6 | import io.github.talelin.latticy.dto.admin.DispatchPermissionsDTO; 7 | import io.github.talelin.latticy.dto.admin.NewGroupDTO; 8 | import io.github.talelin.latticy.dto.admin.RemovePermissionsDTO; 9 | import io.github.talelin.latticy.dto.admin.ResetPasswordDTO; 10 | import io.github.talelin.latticy.dto.admin.UpdateGroupDTO; 11 | import io.github.talelin.latticy.dto.admin.UpdateUserInfoDTO; 12 | import io.github.talelin.latticy.model.GroupDO; 13 | import io.github.talelin.latticy.model.PermissionDO; 14 | import io.github.talelin.latticy.model.UserDO; 15 | 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | /** 20 | * @author pedro@TaleLin 21 | * @author Juzi@TaleLin 22 | * 管理员服务接口 23 | */ 24 | public interface AdminService { 25 | 26 | /** 27 | * 通过分组id分页获取用户数据 28 | * 29 | * @param groupId 分组id 30 | * @param count 当前页数目 31 | * @param page 当前分页 32 | * @return 用户数据 33 | */ 34 | IPage getUserPageByGroupId(Integer groupId, Integer count, Integer page); 35 | 36 | /** 37 | * 修改用户密码(重置用户密码) 38 | * 39 | * @param id 用户id 40 | * @param dto 密码信息 41 | * @return 是否修改成功 42 | */ 43 | boolean changeUserPassword(Integer id, ResetPasswordDTO dto); 44 | 45 | /** 46 | * 删除用户 47 | * 48 | * @param id 用户id 49 | * @return 是否删除成功 50 | */ 51 | boolean deleteUser(Integer id); 52 | 53 | /** 54 | * 更新用户信息 55 | * 56 | * @param id 用户id 57 | * @param dto 数据信息 58 | * @return 是否成功 59 | */ 60 | boolean updateUserInfo(Integer id, UpdateUserInfoDTO dto); 61 | 62 | /** 63 | * 分页获取分组数据 64 | * 65 | * @param page 当前页 66 | * @param count 当前页数量 67 | * @return 分组数据 68 | */ 69 | IPage getGroupPage(Integer page, Integer count); 70 | 71 | /** 72 | * 获得分组数据 73 | * 74 | * @param id 分组id 75 | * @return 分组数据 76 | */ 77 | GroupPermissionBO getGroup(Integer id); 78 | 79 | /** 80 | * 新建分组 81 | * 82 | * @param dto 分组信息 83 | * @return 是否成功 84 | */ 85 | boolean createGroup(NewGroupDTO dto); 86 | 87 | /** 88 | * 更新分组 89 | * 90 | * @param id 分组id 91 | * @param dto 分组信息 92 | * @return 是否成功 93 | */ 94 | boolean updateGroup(Integer id, UpdateGroupDTO dto); 95 | 96 | /** 97 | * 删除分组 98 | * 99 | * @param id 分组id 100 | * @return 是否成功 101 | */ 102 | boolean deleteGroup(Integer id); 103 | 104 | /** 105 | * 分配权限 106 | * 107 | * @param dto 数据 108 | * @return 是否成功 109 | */ 110 | boolean dispatchPermission(DispatchPermissionDTO dto); 111 | 112 | /** 113 | * 分配权限 114 | * 115 | * @param dto 数据 116 | * @return 是否成功 117 | */ 118 | boolean dispatchPermissions(DispatchPermissionsDTO dto); 119 | 120 | /** 121 | * 删除权限 122 | * 123 | * @param dto 数据 124 | * @return 是否成功 125 | */ 126 | boolean removePermissions(RemovePermissionsDTO dto); 127 | 128 | /** 129 | * 获得所有分组信息 130 | */ 131 | List getAllGroups(); 132 | 133 | /** 134 | * 获得所有权限信息 135 | */ 136 | List getAllPermissions(); 137 | 138 | /** 139 | * 获得结构化的权限信息 140 | */ 141 | Map> getAllStructuralPermissions(); 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/service/impl/UserIdentityServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 5 | import io.github.talelin.core.util.EncryptUtil; 6 | import io.github.talelin.latticy.common.constant.IdentityConstant; 7 | import io.github.talelin.latticy.mapper.UserIdentityMapper; 8 | import io.github.talelin.latticy.model.UserIdentityDO; 9 | import io.github.talelin.latticy.service.UserIdentityService; 10 | import org.springframework.stereotype.Service; 11 | 12 | /** 13 | * @author pedro@TaleLin 14 | * @author Juzi@TaleLin 15 | * 用户身份标识服务实现类 16 | */ 17 | @Service 18 | public class UserIdentityServiceImpl extends ServiceImpl implements UserIdentityService { 19 | 20 | 21 | @Override 22 | public UserIdentityDO createIdentity(Integer userId, String identityType, String identifier, String credential) { 23 | UserIdentityDO userIdentity = new UserIdentityDO(); 24 | userIdentity.setUserId(userId); 25 | userIdentity.setIdentityType(identityType); 26 | userIdentity.setIdentifier(identifier); 27 | userIdentity.setCredential(credential); 28 | return this.createIdentity(userIdentity); 29 | } 30 | 31 | @Override 32 | public UserIdentityDO createIdentity(UserIdentityDO userIdentity) { 33 | this.baseMapper.insert(userIdentity); 34 | return userIdentity; 35 | } 36 | 37 | @Override 38 | public UserIdentityDO createUsernamePasswordIdentity(Integer userId, String identifier, String credential) { 39 | // 密码加密 40 | credential = EncryptUtil.encrypt(credential); 41 | return this.createIdentity(userId, IdentityConstant.USERNAME_PASSWORD_IDENTITY, identifier, credential); 42 | } 43 | 44 | @Override 45 | public boolean verifyUsernamePassword(Integer userId, String username, String password) { 46 | QueryWrapper wrapper = new QueryWrapper<>(); 47 | wrapper.lambda().eq(UserIdentityDO::getUserId, userId) 48 | .eq(UserIdentityDO::getIdentityType, IdentityConstant.USERNAME_PASSWORD_IDENTITY) 49 | .eq(UserIdentityDO::getIdentifier, username); 50 | UserIdentityDO userIdentity = this.baseMapper.selectOne(wrapper); 51 | return EncryptUtil.verify(userIdentity.getCredential(), password); 52 | } 53 | 54 | @Override 55 | public boolean changePassword(Integer userId, String password) { 56 | String encrypted = EncryptUtil.encrypt(password); 57 | UserIdentityDO userIdentity = UserIdentityDO.builder().credential(encrypted).build(); 58 | QueryWrapper wrapper = new QueryWrapper<>(); 59 | wrapper.lambda().eq(UserIdentityDO::getUserId, userId); 60 | return this.baseMapper.update(userIdentity, wrapper) > 0; 61 | } 62 | 63 | @Override 64 | public boolean changeUsername(Integer userId, String username) { 65 | UserIdentityDO userIdentity = UserIdentityDO.builder().identifier(username).build(); 66 | QueryWrapper wrapper = new QueryWrapper<>(); 67 | wrapper.lambda().eq(UserIdentityDO::getUserId, userId); 68 | return this.baseMapper.update(userIdentity, wrapper) > 0; 69 | } 70 | 71 | @Override 72 | public boolean changeUsernamePassword(Integer userId, String username, String password) { 73 | UserIdentityDO userIdentity = 74 | UserIdentityDO.builder().identifier(username).credential(password).build(); 75 | QueryWrapper wrapper = new QueryWrapper<>(); 76 | wrapper.lambda().eq(UserIdentityDO::getUserId, userId); 77 | return this.baseMapper.update(userIdentity, wrapper) > 0; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/module/message/WebSocketInterceptor.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.module.message; 2 | 3 | import com.auth0.jwt.exceptions.AlgorithmMismatchException; 4 | import com.auth0.jwt.exceptions.InvalidClaimException; 5 | import com.auth0.jwt.exceptions.JWTDecodeException; 6 | import com.auth0.jwt.exceptions.SignatureVerificationException; 7 | import com.auth0.jwt.exceptions.TokenExpiredException; 8 | import com.auth0.jwt.interfaces.Claim; 9 | import io.github.talelin.core.token.DoubleJWT; 10 | import io.github.talelin.latticy.model.UserDO; 11 | import io.github.talelin.latticy.service.GroupService; 12 | import io.github.talelin.latticy.service.UserService; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.http.HttpStatus; 15 | import org.springframework.http.server.ServerHttpRequest; 16 | import org.springframework.http.server.ServerHttpResponse; 17 | import org.springframework.http.server.ServletServerHttpRequest; 18 | import org.springframework.web.socket.WebSocketHandler; 19 | import org.springframework.web.socket.server.HandshakeInterceptor; 20 | 21 | import java.io.IOException; 22 | import java.nio.charset.StandardCharsets; 23 | import java.util.Map; 24 | 25 | import static io.github.talelin.latticy.module.message.MessageConstant.USER_KEY; 26 | 27 | /** 28 | * @author pedro@TaleLin 29 | * @author Juzi@TaleLin 30 | * websocket 拦截器,主要是jwt鉴权 31 | */ 32 | public class WebSocketInterceptor implements HandshakeInterceptor { 33 | @Autowired 34 | private DoubleJWT jwt; 35 | 36 | @Autowired 37 | private UserService userService; 38 | 39 | @Autowired 40 | private GroupService groupService; 41 | 42 | @Override 43 | public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, Map attributes) throws Exception { 44 | if (request instanceof ServletServerHttpRequest) { 45 | ServletServerHttpRequest httpServletRequest = (ServletServerHttpRequest) request; 46 | String tokenStr = httpServletRequest.getServletRequest().getParameter("token"); 47 | if (tokenStr == null || tokenStr.isEmpty()) { 48 | writeMessageToBody(response, "authorization field is required"); 49 | return false; 50 | } 51 | Map claims; 52 | try { 53 | claims = jwt.decodeAccessToken(tokenStr); 54 | } catch (TokenExpiredException e) { 55 | writeMessageToBody(response, "token is expired"); 56 | return false; 57 | } catch (AlgorithmMismatchException | SignatureVerificationException | JWTDecodeException | InvalidClaimException e) { 58 | writeMessageToBody(response, "token is invalid"); 59 | return false; 60 | } 61 | if (claims == null) { 62 | writeMessageToBody(response, "token is invalid, can't be decode"); 63 | return false; 64 | } 65 | int identity = claims.get("identity").asInt(); 66 | UserDO user = userService.getById(identity); 67 | if (user == null) { 68 | writeMessageToBody(response, "user is not found"); 69 | return false; 70 | } 71 | attributes.put(USER_KEY, user); 72 | return true; 73 | } 74 | return false; 75 | } 76 | 77 | @Override 78 | public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) { 79 | // default implementation ignored 80 | } 81 | 82 | private boolean verifyAdmin(UserDO user) { 83 | return groupService.checkIsRootByUserId(user.getId()); 84 | } 85 | 86 | private void writeMessageToBody(ServerHttpResponse response, String message) throws IOException { 87 | response.setStatusCode(HttpStatus.BAD_REQUEST); 88 | response.getBody().write(message.getBytes(StandardCharsets.UTF_8)); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/service/impl/PermissionServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import io.github.talelin.latticy.mapper.PermissionMapper; 5 | import io.github.talelin.latticy.model.PermissionDO; 6 | import io.github.talelin.latticy.service.PermissionService; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.ArrayList; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * @author pedro@TaleLin 16 | * @author Juzi@TaleLin 17 | * 权限服务实现类 18 | */ 19 | @Service 20 | public class PermissionServiceImpl extends ServiceImpl implements PermissionService { 21 | 22 | 23 | @Override 24 | public List getPermissionByGroupId(Integer groupId) { 25 | return this.baseMapper.selectPermissionsByGroupId(groupId); 26 | } 27 | 28 | @Override 29 | public List getPermissionByGroupIds(List groupIds) { 30 | return this.baseMapper.selectPermissionsByGroupIds(groupIds); 31 | } 32 | 33 | /** 34 | * 为什么不使用联表进行查询? 35 | * 1. 联表很麻烦,需要关联2,3次,涉及到3张表,会严重影响性能 36 | * 2. 由于使用了IN关键字,所以性能其实很不好 37 | * 3. 不直观,可读性差 38 | * 4. 用户的分组一般都比较少,一般情况下都在2个一下 39 | */ 40 | @Override 41 | public Map> getPermissionMapByGroupIds(List groupIds) { 42 | HashMap map = new HashMap(groupIds.size()); 43 | groupIds.stream().forEach(groupId -> { 44 | List permissions = this.baseMapper.selectPermissionsByGroupId(groupId); 45 | map.put(groupId, permissions); 46 | }); 47 | return map; 48 | } 49 | 50 | @Override 51 | public List>>> structuringPermissions(List permissions) { 52 | Map>> tmp = new HashMap(50); 53 | permissions.forEach(permission -> { 54 | if (!tmp.containsKey(permission.getModule())) { 55 | Map tiny = new HashMap(); 56 | tiny.put("module", permission.getModule()); 57 | tiny.put("permission", permission.getName()); 58 | List> mini = new ArrayList(); 59 | mini.add(tiny); 60 | tmp.put(permission.getModule(), mini); 61 | } else { 62 | Map tiny = new HashMap(); 63 | tiny.put("module", permission.getModule()); 64 | tiny.put("permission", permission.getName()); 65 | tmp.get(permission.getModule()).add(tiny); 66 | } 67 | }); 68 | List>>> structualPermissions = new ArrayList(); 69 | tmp.forEach((k, v) -> { 70 | Map>> ttmp = new HashMap(); 71 | ttmp.put(k, v); 72 | structualPermissions.add(ttmp); 73 | }); 74 | return structualPermissions; 75 | } 76 | 77 | @Override 78 | public Map> structuringPermissionsSimply(List permissions) { 79 | // mod permission.names 80 | Map> res = new HashMap<>(); 81 | permissions.forEach(permission -> { 82 | if (res.containsKey(permission.getModule())) { 83 | List mod = res.get(permission.getModule()); 84 | mod.add(permission.getName()); 85 | } else { 86 | List mod = new ArrayList<>(); 87 | mod.add(permission.getName()); 88 | res.put(permission.getModule(), mod); 89 | } 90 | }); 91 | return res; 92 | } 93 | 94 | @Override 95 | public List getPermissionByGroupIdsAndModule(List groupIds, String module) { 96 | return this.baseMapper.selectPermissionsByGroupIdsAndModule(groupIds, module); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/service/UserService.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import io.github.talelin.latticy.common.mybatis.LinPage; 6 | import io.github.talelin.latticy.dto.user.ChangePasswordDTO; 7 | import io.github.talelin.latticy.dto.user.RegisterDTO; 8 | import io.github.talelin.latticy.dto.user.UpdateInfoDTO; 9 | import io.github.talelin.latticy.model.GroupDO; 10 | import io.github.talelin.latticy.model.PermissionDO; 11 | import io.github.talelin.latticy.model.UserDO; 12 | import io.github.talelin.latticy.vo.LoginCaptchaVO; 13 | 14 | import java.awt.*; 15 | import java.io.IOException; 16 | import java.security.GeneralSecurityException; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | /** 21 | * 用户服务实现类 22 | * 23 | * @author pedro@TaleLin 24 | * @author Juzi@TaleLin 25 | */ 26 | public interface UserService extends IService { 27 | 28 | /** 29 | * 新建用户 30 | * 31 | * @param validator 新建用户校验器 32 | * @return 被创建的用户 33 | */ 34 | UserDO createUser(RegisterDTO validator); 35 | 36 | /** 37 | * 更新用户 38 | * 39 | * @param validator 更新用户信息用户校验器 40 | * @return 被更新的用户 41 | */ 42 | UserDO updateUserInfo(UpdateInfoDTO validator); 43 | 44 | /** 45 | * 修改用户密码 46 | * 47 | * @param validator 修改密码校验器 48 | * @return 被修改密码的用户 49 | */ 50 | UserDO changeUserPassword(ChangePasswordDTO validator); 51 | 52 | /** 53 | * 获得用户的所有分组 54 | * 55 | * @param userId 用户id 56 | * @return 所有分组 57 | */ 58 | List getUserGroups(Integer userId); 59 | 60 | /** 61 | * 获得用户所有权限 62 | * 63 | * @param userId 用户id 64 | * @return 权限 65 | */ 66 | List>>> getStructuralUserPermissions(Integer userId); 67 | 68 | /** 69 | * 获得用户所有权限 70 | * 71 | * @param userId 用户id 72 | * @return 权限 73 | */ 74 | List getUserPermissions(Integer userId); 75 | 76 | 77 | /** 78 | * 获得用户在模块下的所有权限 79 | * 80 | * @param userId 用户id 81 | * @param module 权限模块 82 | * @return 权限 83 | */ 84 | List getUserPermissionsByModule(Integer userId, String module); 85 | 86 | 87 | /** 88 | * 通过用户名查找用户 89 | * 90 | * @param username 用户名 91 | * @return 用户 92 | */ 93 | UserDO getUserByUsername(String username); 94 | 95 | /** 96 | * 根据用户名检查用户是否存在 97 | * 98 | * @param username 用户名 99 | * @return true代表存在 100 | */ 101 | boolean checkUserExistByUsername(String username); 102 | 103 | 104 | /** 105 | * 根据用户名检查用户是否存在 106 | * 107 | * @param email 邮箱 108 | * @return true代表存在 109 | */ 110 | boolean checkUserExistByEmail(String email); 111 | 112 | /** 113 | * 根据用户id检查用户是否存在 114 | * 115 | * @param id 用户名 116 | * @return true代表存在 117 | */ 118 | boolean checkUserExistById(Integer id); 119 | 120 | /** 121 | * 根据分组id分页获取用户数据 122 | * 123 | * @param pager 分页 124 | * @param groupId 分组id 125 | * @return 数据页 126 | */ 127 | IPage getUserPageByGroupId(LinPage pager, Integer groupId); 128 | 129 | 130 | /** 131 | * 获取超级管理员的id 132 | * 133 | * @return 超级管理员的id 134 | */ 135 | Integer getRootUserId(); 136 | 137 | /** 138 | * 生成无状态的登录验证码 139 | * 140 | * @return LoginCaptchaVO 验证码视图对象 141 | * @throws IOException 142 | * @throws FontFormatException 143 | * @throws GeneralSecurityException 144 | */ 145 | LoginCaptchaVO generateCaptcha() throws IOException, FontFormatException, GeneralSecurityException; 146 | 147 | /** 148 | * 校验登录验证码 149 | * 150 | * @return 结果 151 | */ 152 | boolean verifyCaptcha(String captcha, String tag); 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/io/github/talelin/latticy/common/configuration/WebConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.common.configuration; 2 | 3 | import io.github.talelin.latticy.common.interceptor.RequestLogInterceptor; 4 | import io.github.talelin.autoconfigure.interceptor.AuthorizeInterceptor; 5 | import io.github.talelin.autoconfigure.interceptor.LogInterceptor; 6 | import io.github.talelin.latticy.module.file.FileUtil; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 12 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 13 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 14 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 15 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 16 | 17 | import java.nio.file.FileSystems; 18 | import java.nio.file.Path; 19 | import java.util.List; 20 | 21 | /** 22 | * Spring MVC 配置 23 | * 24 | * @author pedro@TaleLin 25 | * @author colorful@TaleLin 26 | */ 27 | @Configuration(proxyBeanMethods = false) 28 | @Slf4j 29 | public class WebConfiguration implements WebMvcConfigurer { 30 | 31 | @Value("${auth.enabled:false}") 32 | private boolean authEnabled; 33 | 34 | @Value("${request-log.enabled:false}") 35 | private boolean requestLogEnabled; 36 | 37 | @Autowired 38 | private AuthorizeInterceptor authorizeInterceptor; 39 | 40 | @Autowired 41 | private LogInterceptor logInterceptor; 42 | 43 | @Autowired 44 | private RequestLogInterceptor requestLogInterceptor; 45 | 46 | @Value("${lin.file.store-dir:assets/}") 47 | private String dir; 48 | 49 | @Value("${lin.file.serve-path:assets/**}") 50 | private String servePath; 51 | 52 | /** 53 | * 跨域 54 | * 注意: 跨域问题涉及安全性问题,这里提供的是最方便简单的配置,任何host和任何方法都可跨域 55 | * 但在实际场景中,这样做,无疑很危险,所以谨慎选择开启或者关闭 56 | * 如果切实需要,请咨询相关安全人员或者专家进行配置 57 | */ 58 | @Override 59 | public void addCorsMappings(CorsRegistry registry) { 60 | registry.addMapping("/**") 61 | .allowedOriginPatterns("*") 62 | .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS") 63 | .allowCredentials(true) 64 | .maxAge(3600) 65 | .allowedHeaders("*"); 66 | } 67 | 68 | @Override 69 | public void addInterceptors(InterceptorRegistry registry) { 70 | if (authEnabled) { 71 | //开发环境忽略签名认证 72 | registry.addInterceptor(authorizeInterceptor) 73 | .excludePathPatterns(getDirServePath()); 74 | } 75 | if (requestLogEnabled) { 76 | registry.addInterceptor(requestLogInterceptor); 77 | } 78 | registry.addInterceptor(logInterceptor); 79 | } 80 | 81 | @Override 82 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 83 | // classpath: or file: 84 | registry.addResourceHandler(getDirServePath()) 85 | .addResourceLocations("file:" + getAbsDir() + "/"); 86 | } 87 | 88 | /** 89 | * request parameter 转 java bean 时 snake_case 转 camelCase 90 | */ 91 | @Override 92 | public void addArgumentResolvers(List resolvers) { 93 | resolvers.add(new CustomServletModelAttributeMethodProcessor(true)); 94 | } 95 | 96 | private String getDirServePath() { 97 | // assets/** 98 | // assets/ 99 | // /usr/local/assets/ 100 | // assets 101 | return servePath; 102 | } 103 | 104 | /** 105 | * 获得文件夹的绝对路径 106 | */ 107 | private String getAbsDir() { 108 | if (FileUtil.isAbsolute(dir)) { 109 | return dir; 110 | } 111 | String cmd = System.getProperty("user.dir"); 112 | Path path = FileSystems.getDefault().getPath(cmd, dir); 113 | return path.toAbsolutePath().toString(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/resources/code-message.properties: -------------------------------------------------------------------------------- 1 | # \u6D88\u606F\u7801\u914D\u7F6E\u6587\u4EF6 2 | # \u683C\u5F0F\uFF1A\u6D88\u606F\u7801 -> \u6D88\u606F 3 | code-message[0]=\u6210\u529F 4 | code-message[1]=\u521B\u5EFA\u6210\u529F 5 | code-message[2]=\u66F4\u65B0\u6210\u529F 6 | code-message[3]=\u5220\u9664\u6210\u529F 7 | code-message[4]=\u5BC6\u7801\u4FEE\u6539\u6210\u529F 8 | code-message[5]=\u5220\u9664\u7528\u6237\u6210\u529F 9 | code-message[6]=\u66F4\u65B0\u7528\u6237\u6210\u529F 10 | code-message[7]=\u66F4\u65B0\u5206\u7EC4\u6210\u529F 11 | code-message[8]=\u5220\u9664\u5206\u7EC4\u6210\u529F 12 | code-message[9]=\u6DFB\u52A0\u6743\u9650\u6210\u529F 13 | code-message[10]=\u5220\u9664\u6743\u9650\u6210\u529F 14 | code-message[11]=\u6CE8\u518C\u6210\u529F 15 | code-message[12]=\u65B0\u5EFA\u56FE\u4E66\u6210\u529F 16 | code-message[13]=\u66F4\u65B0\u56FE\u4E66\u6210\u529F 17 | code-message[14]=\u5220\u9664\u56FE\u4E66\u6210\u529F 18 | code-message[15]=\u65B0\u5EFA\u5206\u7EC4\u6210\u529F 19 | code-message[9999]=\u670D\u52A1\u5668\u672A\u77E5\u9519\u8BEF 20 | code-message[10000]=\u672A\u643A\u5E26\u4EE4\u724C 21 | code-message[10001]=\u6743\u9650\u4E0D\u8DB3 22 | code-message[10010]=\u6388\u6743\u5931\u8D25 23 | code-message[10011]=\u66F4\u65B0\u5BC6\u7801\u5931\u8D25 24 | code-message[10012]=\u8BF7\u4F20\u5165\u8BA4\u8BC1\u5934\u5B57\u6BB5 25 | code-message[10013]=\u8BA4\u8BC1\u5934\u5B57\u6BB5\u89E3\u6790\u5931\u8D25 26 | code-message[10020]=\u8D44\u6E90\u4E0D\u5B58\u5728 27 | code-message[10021]=\u7528\u6237\u4E0D\u5B58\u5728 28 | code-message[10022]=\u672A\u627E\u5230\u76F8\u5173\u4E66\u7C4D 29 | code-message[10023]=\u5206\u7EC4\u4E0D\u5B58\u5728\uFF0C\u65E0\u6CD5\u65B0\u5EFA\u7528\u6237 30 | code-message[10024]=\u5206\u7EC4\u4E0D\u5B58\u5728 31 | code-message[10025]=\u627E\u4E0D\u5230\u76F8\u5E94\u7684\u89C6\u56FE\u5904\u7406\u5668 32 | code-message[10026]=\u672A\u627E\u5230\u6587\u4EF6 33 | code-message[10027]=\u5206\u7EC4\u4E0B\u5B58\u5728\u7528\u6237\uFF0C\u4E0D\u53EF\u5220\u9664\u5F53\u524D\u5206\u7EC4 34 | code-message[10030]=\u53C2\u6570\u9519\u8BEF 35 | code-message[10031]=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF 36 | code-message[10032]=\u8BF7\u8F93\u5165\u6B63\u786E\u7684\u5BC6\u7801 37 | code-message[10040]=\u4EE4\u724C\u5931\u6548 38 | code-message[10041]=access token \u635F\u574F 39 | code-message[10042]=refresh token \u635F\u574F 40 | code-message[10050]=\u4EE4\u724C\u8FC7\u671F 41 | code-message[10051]=access token \u8FC7\u671F 42 | code-message[10052]=refresh token \u8FC7\u671F 43 | code-message[10060]=\u5B57\u6BB5\u91CD\u590D 44 | code-message[10070]=\u7981\u6B62\u64CD\u4F5C 45 | code-message[10071]=\u5DF2\u7ECF\u6709\u7528\u6237\u4F7F\u7528\u4E86\u8BE5\u540D\u79F0\uFF0C\u8BF7\u91CD\u65B0\u8F93\u5165\u65B0\u7684\u7528\u6237\u540D 46 | code-message[10072]=\u5206\u7EC4\u540D\u5DF2\u88AB\u4F7F\u7528\uFF0C\u8BF7\u91CD\u65B0\u586B\u5165\u65B0\u7684\u5206\u7EC4\u540D 47 | code-message[10073]=root\u5206\u7EC4\u4E0D\u53EF\u6DFB\u52A0\u7528\u6237 48 | code-message[10074]=root\u5206\u7EC4\u4E0D\u53EF\u5220\u9664 49 | code-message[10075]=guest\u5206\u7EC4\u4E0D\u53EF\u5220\u9664 50 | code-message[10076]=\u90AE\u7BB1\u5DF2\u88AB\u4F7F\u7528\uFF0C\u8BF7\u91CD\u65B0\u586B\u5165\u65B0\u7684\u90AE\u7BB1 51 | code-message[10077]=\u4E0D\u53EF\u5C06\u7528\u6237\u5206\u914D\u7ED9\u4E0D\u5B58\u5728\u7684\u5206\u7EC4 52 | code-message[10078]=\u4E0D\u53EF\u4FEE\u6539root\u7528\u6237\u7684\u5206\u7EC4 53 | code-message[10079]=root\u5206\u7EC4\u7684\u7528\u6237\u4E0D\u53EF\u5220\u9664 54 | code-message[10080]=\u8BF7\u6C42\u65B9\u6CD5\u4E0D\u5141\u8BB8 55 | code-message[10100]=\u5237\u65B0\u4EE4\u724C\u83B7\u53D6\u5931\u8D25 56 | code-message[10110]=\u6587\u4EF6\u4F53\u79EF\u8FC7\u5927 57 | code-message[10120]=\u6587\u4EF6\u6570\u91CF\u8FC7\u591A 58 | code-message[10121]=\u6587\u4EF6\u592A\u591A\uFF0C\u6587\u4EF6\u603B\u6570\u4E0D\u53EF\u8D85\u8FC7${lin.file.nums} 59 | code-message[10130]=\u6587\u4EF6\u6269\u5C55\u540D\u4E0D\u7B26\u5408\u89C4\u8303 60 | code-message[10140]=\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5 61 | code-message[10150]=\u4E22\u5931\u53C2\u6570 62 | code-message[10160]=\u7C7B\u578B\u9519\u8BEF 63 | code-message[10170]=\u8BF7\u6C42\u4F53\u4E0D\u53EF\u4E3A\u7A7A 64 | code-message[10180]=\u5168\u90E8\u6587\u4EF6\u5927\u5C0F\u4E0D\u80FD\u8D85\u8FC7 65 | code-message[10190]=\u8BFB\u53D6\u6587\u4EF6\u6570\u636E\u5931\u8D25 66 | code-message[10200]=\u5931\u8D25 67 | code-message[10260]=\u8BF7\u8F93\u5165\u6B63\u786E\u7684\u9A8C\u8BC1\u7801 -------------------------------------------------------------------------------- /src/test/java/io/github/talelin/latticy/service/impl/UserIdentityServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package io.github.talelin.latticy.service.impl; 2 | 3 | import io.github.talelin.core.util.EncryptUtil; 4 | import io.github.talelin.latticy.common.constant.IdentityConstant; 5 | import io.github.talelin.latticy.model.UserIdentityDO; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.junit.jupiter.api.Test; 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.transaction.annotation.Transactional; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertNotNull; 15 | import static org.junit.jupiter.api.Assertions.assertTrue; 16 | 17 | 18 | @SpringBootTest 19 | @Transactional 20 | @Rollback 21 | @Slf4j 22 | @ActiveProfiles("test") 23 | public class UserIdentityServiceImplTest { 24 | 25 | @Autowired 26 | private UserIdentityServiceImpl userIdentityService; 27 | 28 | public UserIdentityDO setUp1() { 29 | UserIdentityDO userIdentity = new UserIdentityDO(); 30 | userIdentity.setUserId(1); 31 | userIdentity.setIdentityType(IdentityConstant.USERNAME_PASSWORD_IDENTITY); 32 | userIdentity.setIdentifier("pedro"); 33 | userIdentity.setCredential(EncryptUtil.encrypt("123456")); 34 | return userIdentity; 35 | } 36 | 37 | public UserIdentityDO setUp2(Integer userId, String identityType, String identifier, String credential) { 38 | UserIdentityDO userIdentity = new UserIdentityDO(); 39 | userIdentity.setUserId(userId); 40 | userIdentity.setIdentityType(identityType); 41 | userIdentity.setIdentifier(identifier); 42 | userIdentity.setCredential(EncryptUtil.encrypt(credential)); 43 | return userIdentity; 44 | } 45 | 46 | @Test 47 | public void createIdentity() { 48 | UserIdentityDO userIdentity = userIdentityService.createIdentity( 49 | 1, 50 | IdentityConstant.USERNAME_PASSWORD_IDENTITY, 51 | "pedro", 52 | EncryptUtil.encrypt("123456") 53 | ); 54 | assertNotNull(userIdentity.getId()); 55 | assertTrue(EncryptUtil.verify(userIdentity.getCredential(), "123456")); 56 | } 57 | 58 | @Test 59 | public void createIdentity1() { 60 | UserIdentityDO userIdentity = setUp1(); 61 | userIdentityService.createIdentity(userIdentity); 62 | assertNotNull(userIdentity.getId()); 63 | assertTrue(EncryptUtil.verify(userIdentity.getCredential(), "123456")); 64 | } 65 | 66 | @Test 67 | public void createUsernamePasswordIdentity() { 68 | UserIdentityDO userIdentity = userIdentityService.createUsernamePasswordIdentity( 69 | 1, 70 | "pedro", 71 | "123456"); 72 | assertNotNull(userIdentity.getId()); 73 | assertTrue(EncryptUtil.verify(userIdentity.getCredential(), "123456")); 74 | } 75 | 76 | // @Test 77 | public void verifyUsernamePassword() { 78 | UserIdentityDO userIdentity = setUp1(); 79 | userIdentityService.createIdentity(userIdentity); 80 | 81 | boolean valid = userIdentityService.verifyUsernamePassword(userIdentity.getUserId(), "pedro", "123456"); 82 | assertTrue(valid); 83 | } 84 | 85 | // @Test 86 | public void changePassword() { 87 | UserIdentityDO userIdentity = setUp1(); 88 | userIdentityService.createIdentity(userIdentity); 89 | 90 | boolean b = userIdentityService.changePassword(userIdentity.getUserId(), "147258"); 91 | assertTrue(b); 92 | 93 | boolean valid = userIdentityService.verifyUsernamePassword(userIdentity.getUserId(), "pedro", "147258"); 94 | assertTrue(valid); 95 | } 96 | 97 | // @Test 98 | public void changeUsername() { 99 | UserIdentityDO userIdentity = setUp1(); 100 | userIdentityService.createIdentity(userIdentity); 101 | 102 | boolean b = userIdentityService.changeUsername(userIdentity.getUserId(), "pedro1"); 103 | assertTrue(b); 104 | 105 | boolean valid = userIdentityService.verifyUsernamePassword(userIdentity.getUserId(), "pedro1", "123456"); 106 | assertTrue(valid); 107 | } 108 | } --------------------------------------------------------------------------------