├── .gitignore ├── LICENSE ├── README.md ├── backstage ├── Dockerfile ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── cn │ │ │ ├── BackstageApplication.java │ │ │ ├── config │ │ │ ├── DruidConfig.java │ │ │ ├── ExceptionHandle.java │ │ │ ├── LoginFailureHandler.java │ │ │ ├── LoginSuccessHandler.java │ │ │ ├── MyAuthenticationDetailsSource.java │ │ │ ├── MyAuthenticationProvider.java │ │ │ ├── MyAuthorizationManager.java │ │ │ └── WebSecurityConfig.java │ │ │ └── controller │ │ │ ├── IndexController.java │ │ │ ├── MsController.java │ │ │ └── SystemController.java │ └── resources │ │ ├── application-dev.yml │ │ ├── application-local.yml │ │ ├── application-pro.yml │ │ ├── application.yml │ │ ├── banner.txt │ │ ├── data.sql │ │ ├── logback-spring.xml │ │ ├── static │ │ ├── css │ │ │ ├── login.css │ │ │ ├── main.css │ │ │ ├── metroStyle │ │ │ │ ├── img │ │ │ │ │ ├── line_conn.png │ │ │ │ │ ├── loading.gif │ │ │ │ │ ├── metro.gif │ │ │ │ │ └── metro.png │ │ │ │ └── metroStyle.css │ │ │ └── public.css │ │ ├── images │ │ │ ├── cloud10.png │ │ │ ├── favicon.ico │ │ │ ├── wx.jpg │ │ │ └── zfb.jpg │ │ ├── js │ │ │ ├── Detector.js │ │ │ ├── index.js │ │ │ ├── jquery.ztree.core.min.js │ │ │ ├── jquery.ztree.excheck.min.js │ │ │ └── three.min.js │ │ └── media │ │ │ ├── login.mp4 │ │ │ └── login.webm │ │ └── templates │ │ ├── carousel │ │ ├── carouselCategoryEdit.html │ │ ├── carouselEdit.html │ │ └── carouselList.html │ │ ├── classify │ │ ├── classifyEdit.html │ │ └── classifyList.html │ │ ├── error │ │ ├── 403.html │ │ ├── 404.html │ │ └── 500.html │ │ ├── essay │ │ ├── essayEdit.html │ │ └── essayList.html │ │ ├── home.html │ │ ├── index.html │ │ ├── login.html │ │ ├── manager │ │ ├── managerDetail.html │ │ ├── managerEdit.html │ │ ├── managerList.html │ │ └── updatePwd.html │ │ ├── menu │ │ ├── menuEdit.html │ │ └── menuList.html │ │ ├── notice │ │ ├── noticeEdit.html │ │ └── noticeList.html │ │ ├── other │ │ └── clouds.html │ │ ├── role │ │ ├── grant.html │ │ ├── roleEdit.html │ │ └── roleList.html │ │ ├── system │ │ └── logList.html │ │ └── user │ │ ├── userDetail.html │ │ ├── userEdit.html │ │ └── userList.html │ └── test │ └── java │ └── com │ └── cn │ ├── BackstageApplicationTests.java │ └── controller │ └── IndexControllerTest.java ├── build.gradle ├── commons ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── cn │ │ ├── config │ │ ├── AsyncPoolConfig.java │ │ ├── JacksonConfig.java │ │ ├── OssAutoConfig.java │ │ ├── OssConfigProperties.java │ │ ├── OssTemplate.java │ │ ├── RabbitConfig.java │ │ ├── RestClientConfig.java │ │ └── WebSocketConfig.java │ │ ├── enums │ │ ├── ConfigEnum.java │ │ ├── EssayStatusEnum.java │ │ ├── FaveTypeEnum.java │ │ ├── GenderEnum.java │ │ ├── LoginStatusEnum.java │ │ ├── ResourceEnum.java │ │ ├── SocialEnum.java │ │ ├── SocialParamEnum.java │ │ ├── UserStatusEnum.java │ │ └── UserTypeEnum.java │ │ ├── exception │ │ ├── FileUploadException.java │ │ └── GlobalException.java │ │ ├── model │ │ ├── AuthenticationDetails.java │ │ ├── CaptchaInfo.java │ │ ├── MenuDTO.java │ │ └── RestCode.java │ │ └── util │ │ ├── IpUtil.java │ │ ├── JacksonUtil.java │ │ ├── MailUtil.java │ │ ├── MenuUtil.java │ │ ├── Result.java │ │ └── UploadUtil.java │ └── test │ └── java │ ├── IpUtilTest.java │ └── JasyptTest.java ├── data.sql ├── docker-compose.yml ├── fluent.conf ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── interface ├── Dockerfile ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── cn │ │ ├── InterfaceApplication.java │ │ ├── amqp │ │ └── MqReceive.java │ │ ├── config │ │ ├── CorsConfig.java │ │ ├── ExceptionHandle.java │ │ ├── JwtAuthenticationEntryPoint.java │ │ ├── JwtAuthenticationFilter.java │ │ ├── JwtLoginFilter.java │ │ ├── JwtTokenUtil.java │ │ ├── MyAuthenticationToken.java │ │ ├── SocialAuthenticationProvider.java │ │ ├── SwaggerConfig.java │ │ ├── UnauthorizedEntryPoint.java │ │ └── WebSecurityConfig.java │ │ ├── constant │ │ └── LoginType.java │ │ ├── controller │ │ ├── IndexController.java │ │ └── UserController.java │ │ └── model │ │ ├── EssayDTO.java │ │ ├── LogInDTO.java │ │ ├── SignUpDTO.java │ │ └── UserVO.java │ └── resources │ ├── application.yml │ ├── banner.txt │ ├── logback-spring.xml │ └── templates │ └── activation.html ├── jetbrains.png ├── jetbrains.svg ├── logstash.conf ├── logstash └── logstash.conf ├── repository ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── cn │ ├── config │ ├── AbstractDateAudit.java │ ├── AbstractUserDateAudit.java │ └── AuditingConfig.java │ ├── dao │ ├── BookRepository.java │ ├── CarouselRepository.java │ ├── ClassifyRepository.java │ ├── CommentRepository.java │ ├── CustomizeRepository.java │ ├── EssayRepository.java │ ├── LoginLogRepository.java │ ├── ManagerRepository.java │ ├── NoticeRepository.java │ ├── PermissionRepository.java │ ├── RoleRepository.java │ ├── SocialInfoRepository.java │ ├── UserFavesRepository.java │ ├── UserFollowRepository.java │ └── UserRepository.java │ └── entity │ ├── Book.java │ ├── Carousel.java │ ├── CarouselCategory.java │ ├── Classify.java │ ├── Comment.java │ ├── Daily.java │ ├── Essay.java │ ├── LoginLog.java │ ├── Manager.java │ ├── Music.java │ ├── News.java │ ├── Notice.java │ ├── Permission.java │ ├── Role.java │ ├── SocialInfo.java │ ├── User.java │ ├── UserFaves.java │ └── UserFollow.java ├── service ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── cn │ ├── BookService.java │ ├── CarouselService.java │ ├── ClassifyService.java │ ├── EssayService.java │ ├── LogService.java │ ├── ManagerService.java │ ├── NoticeService.java │ ├── RoleService.java │ ├── UserRelatedService.java │ └── UserService.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | .gradle 25 | build/ 26 | !gradle/wrapper/gradle-wrapper.jar 27 | 28 | ### STS ### 29 | .apt_generated 30 | .classpath 31 | .factorypath 32 | .project 33 | .settings 34 | .springBeans 35 | 36 | ### IntelliJ IDEA ### 37 | .idea 38 | *.iws 39 | *.iml 40 | *.ipr 41 | out/* 42 | out/ 43 | target/ 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MusicStory 2 | 一个用来听歌看书的网站 3 | - 基本框架:springboot3.0全家桶 ,gradle构建 4 | - 没弄微服务分布式,麻烦,用不上 5 | - 后台页面采用 thymeleaf 模板 6 | - 以layui框架为页面布局主题 7 | - 数据库:mysql 8 | - 数据连接池:阿里druid 9 | - 缓存:Redis 10 | - 消息队列:RabbitMQ 11 | - 消息推送:WebSocket 12 | - 全文搜索:ElasticSearch 13 | 14 | ## 模块概述 15 | 16 | | 模块名称 | 功能 | 17 | |:--|:--| 18 | | backstage | 后台管理端 | 19 | | commons | 公共代码部分 | 20 | | interface | 前端API接口 | 21 | | repository | 数据DAO层 | 22 | | service | 业务service层 | 23 | 24 | 前端代码链接:https://github.com/ngcly/music-story 25 | 26 | 为了方便下载项目代码后直接运行启动,建议先安装docker: 27 | 项目目前已经添加了spring-boot-docker-compose支持 28 | 项目启动时会根据docker-compose.yml的配置自动创建docker容器 29 | 容器创建完并启动后 部分组件需要安装插件 30 | 31 | 1.安装RabbitMQ延时队列插件: 32 | 1. 下载rabbitmq_delayed_message_exchange插件 下载地址:https://www.rabbitmq.com/community-plugins.html 33 | 2. 将下载的插件拷贝到容器中 34 | ``` 35 | docker cp rabbitmq_delayed_message_exchange-3.10.2.ez rabbitmq:/plugins 36 | ``` 37 | 3. 启动插件并重启容器 38 | ``` 39 | rabbitmq-plugins enable rabbitmq_delayed_message_exchange 40 | ``` 41 | 2.若有使用fluentd则需要安装插件(自行配置可选): 42 | ``` 43 | docker exec -it --user root efk-fluentd /bin/sh 44 | fluent-gem install fluent-plugin-elasticsearch 45 | ``` 46 | 47 | ### SpringBoot3.0 已知存在以下问题 48 | 1. ~~Druid组件监控页面无法使用~~ 49 | 2. ~~Jasypt配置文件加解密无效~~ 50 | 3. ~~Hutool还未适配,SpringUtil无法正常使用~~ 51 | 52 | 特别鸣谢: 53 | 感谢JetBrains免费提供Idea开发工具对本项目的支持 54 | [![JetBrains](jetbrains.svg "jetbrains")](https://www.jetbrains.com/?from=MusicStory) -------------------------------------------------------------------------------- /backstage/Dockerfile: -------------------------------------------------------------------------------- 1 | # FROM 表示使用 Jdk17 环境 为基础镜像,如果镜像不是本地的会从 DockerHub 进行下载 2 | FROM eclipse-temurin:17.0.3_7-jre 3 | # VOLUME 指向了一个/tmp的目录,由于 Spring Boot 使用内置的Tomcat容器,Tomcat 默认使用/tmp作为工作目录。这个命令的效果是:在宿主机的/var/lib/docker目录下创建一个临时文件并把它链接到容器中的/tmp目录 4 | VOLUME /tmp 5 | 6 | ARG PROJECT_NAME=backstage 7 | ARG JAR_FILE=build/libs/${PROJECT_NAME}-*.jar 8 | ENV APP_JAR=${PROJECT_NAME}-ms.jar 9 | COPY ${JAR_FILE} ${APP_JAR} 10 | ENTRYPOINT java -jar ${APP_JAR} -------------------------------------------------------------------------------- /backstage/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { 2 | archiveBaseName = "backstage" 3 | archiveVersion = '1.0' 4 | } 5 | 6 | dependencies { 7 | implementation project(":service") 8 | 9 | //后台模板jar包 10 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 11 | 12 | //弱化 thymeleaf 校验 13 | implementation "net.sourceforge.nekohtml:nekohtml:${nekohtmlVersion}" 14 | //thymeleaf 扩展 15 | implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' 16 | // implementation "org.thymeleaf.extras:thymeleaf-extras-java8time:${extrasVersion}" 17 | implementation "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:${dialectVersion}" 18 | //js包 19 | implementation "org.webjars:webjars-locator-lite:${locatorVersion}" 20 | runtimeOnly "org.webjars.npm:layui:${layuiVersion}" 21 | runtimeOnly "org.webjars.npm:jquery:${jqueryVersion}" 22 | runtimeOnly "org.webjars.npm:jquery-treegrid:${treegridVersion}" 23 | } 24 | -------------------------------------------------------------------------------- /backstage/src/main/java/com/cn/BackstageApplication.java: -------------------------------------------------------------------------------- 1 | package com.cn; 2 | 3 | import com.cn.enums.ConfigEnum; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cache.annotation.EnableCaching; 7 | 8 | /** 9 | * 后台主启动类 10 | *

11 | * springboot 启动类不能直接放在 java 文件下 不然会报错 12 | * springboot 扫描包默认是以启动类所在的包 从上往下扫描 13 | * 要解决这个可以使用:basePackageClasses={},basePackages={} 14 | * @ComponentScan(basePackages={"com.cn"}) 15 | * 16 | * @author ngcly 17 | * @since 2017-12-30 15:51 18 | */ 19 | @EnableCaching 20 | @SpringBootApplication 21 | public class BackstageApplication { 22 | public static void main(String[] args) { 23 | ConfigEnum configEnum = ConfigEnum.JASYPT_ENCRYPTOR; 24 | System.setProperty(configEnum.getKey(),configEnum.getValue()); 25 | SpringApplication.run(BackstageApplication.class, args); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /backstage/src/main/java/com/cn/config/DruidConfig.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure; 4 | import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties; 5 | import com.alibaba.druid.util.Utils; 6 | import jakarta.servlet.*; 7 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 10 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | 14 | /** 15 | * @author chenning 16 | * 去除druid监控页面底部的广告 17 | */ 18 | @Configuration 19 | @ConditionalOnWebApplication 20 | @AutoConfigureAfter(DruidDataSourceAutoConfigure.class) 21 | @ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true", 22 | matchIfMissing = true) 23 | public class DruidConfig { 24 | 25 | @Bean 26 | public FilterRegistrationBean removeDruidAndFilterRegistrationBean(DruidStatProperties properties) { 27 | // 获取web监控页面的参数 28 | DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); 29 | // 提取common.js的配置路径 30 | String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; 31 | String commonJsPattern = pattern.replaceAll("(\\*)+", "js/common.js"); 32 | 33 | return getFilterFilterRegistrationBean(commonJsPattern); 34 | } 35 | 36 | private FilterRegistrationBean getFilterFilterRegistrationBean(String commonJsPattern) { 37 | Filter filter = (request, response, chain) -> { 38 | chain.doFilter(request, response); 39 | // 重置置缓冲区,响应头不会被重置 40 | response.resetBuffer(); 41 | // 获取common.js 42 | String text = Utils.readFromResource("support/http/resources/js/common.js"); 43 | text = text.replaceAll("
", ""); 44 | text = text.replaceAll("powered.*?shrek.wang", ""); 45 | response.getWriter().write(text); 46 | }; 47 | FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); 48 | registrationBean.setFilter(filter); 49 | registrationBean.addUrlPatterns(commonJsPattern); 50 | return registrationBean; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /backstage/src/main/java/com/cn/config/ExceptionHandle.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import cn.hutool.log.Log; 4 | import cn.hutool.log.LogFactory; 5 | import com.cn.exception.GlobalException; 6 | import com.cn.model.RestCode; 7 | import com.cn.util.Result; 8 | import org.apache.http.HttpHeaders; 9 | import org.apache.http.entity.ContentType; 10 | import org.springframework.dao.DataIntegrityViolationException; 11 | import org.springframework.security.access.AccessDeniedException; 12 | import org.springframework.validation.FieldError; 13 | import org.springframework.web.HttpMediaTypeNotAcceptableException; 14 | import org.springframework.web.HttpRequestMethodNotSupportedException; 15 | import org.springframework.web.bind.MethodArgumentNotValidException; 16 | import org.springframework.web.bind.MissingPathVariableException; 17 | import org.springframework.web.bind.MissingServletRequestParameterException; 18 | import org.springframework.web.bind.annotation.ControllerAdvice; 19 | import org.springframework.web.bind.annotation.ExceptionHandler; 20 | import org.springframework.web.bind.annotation.ResponseBody; 21 | 22 | import jakarta.servlet.http.HttpServletRequest; 23 | import jakarta.validation.ConstraintViolationException; 24 | import java.util.stream.Collectors; 25 | 26 | /** 27 | * 全局异常处理 28 | * @author ngcly 29 | * @since 2019/5/18 14:53 30 | */ 31 | @ControllerAdvice 32 | public class ExceptionHandle { 33 | private static final Log log = LogFactory.get(); 34 | 35 | @ResponseBody 36 | @ExceptionHandler(value = Exception.class) 37 | public Result handlerException(HttpServletRequest request, Exception e) throws Exception { 38 | log.error(e); 39 | if (request.getHeader(HttpHeaders.ACCEPT).contains(ContentType.APPLICATION_JSON.getMimeType()) 40 | || "XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) { 41 | return getResult(e); 42 | }else{ 43 | throw e; 44 | } 45 | } 46 | 47 | public Result getResult(Exception e){ 48 | if(e instanceof AccessDeniedException){ 49 | return Result.failure(RestCode.UNAUTHORIZED); 50 | } else if (e instanceof DataIntegrityViolationException){ 51 | return Result.failure(RestCode.UNION_DUMP); 52 | } else if (e instanceof HttpRequestMethodNotSupportedException) { 53 | return Result.failure(RestCode.METHOD_ERROR); 54 | } else if (e instanceof MissingPathVariableException) { 55 | // 缺少路径参数 56 | return Result.failure(RestCode.NOT_FOUND); 57 | } else if (e instanceof MissingServletRequestParameterException) { 58 | // 缺少必须的请求参数 59 | return Result.failure(RestCode.PARAM_ERROR); 60 | } else if (e instanceof HttpMediaTypeNotAcceptableException){ 61 | return Result.failure(RestCode.HEAD_ERROR); 62 | } else if (e instanceof GlobalException globalException){ 63 | return Result.failure(globalException.getCode(), globalException.getMessage()); 64 | } else if (e instanceof ConstraintViolationException exception) { 65 | //@RequestParam 参数校验失败 66 | String msg = exception.getConstraintViolations().stream().map(constraint -> 67 | constraint.getInvalidValue()+":"+constraint.getMessage()).collect(Collectors.joining(";")); 68 | return Result.failure(RestCode.PARAM_ERROR.code, msg); 69 | } else if (e instanceof MethodArgumentNotValidException exception){ 70 | StringBuilder errMsg = new StringBuilder(); 71 | String msg = exception.getBindingResult().getAllErrors().stream().map(objectError -> { 72 | if(objectError instanceof FieldError fieldError){ 73 | errMsg.append(fieldError.getField()).append(":"); 74 | } 75 | errMsg.append(objectError.getDefaultMessage()==null?"":objectError.getDefaultMessage()); 76 | return errMsg; 77 | }).collect(Collectors.joining(";")); 78 | return Result.failure(RestCode.PARAM_ERROR.code, msg); 79 | } else { 80 | return Result.failure(RestCode.SERVER_ERROR); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /backstage/src/main/java/com/cn/config/LoginFailureHandler.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import com.cn.model.RestCode; 4 | import com.cn.util.JacksonUtil; 5 | import com.cn.util.Result; 6 | import org.apache.http.entity.ContentType; 7 | import org.springframework.security.authentication.*; 8 | import org.springframework.security.core.AuthenticationException; 9 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 10 | import org.springframework.security.web.authentication.AuthenticationFailureHandler; 11 | 12 | import jakarta.servlet.http.HttpServletRequest; 13 | import jakarta.servlet.http.HttpServletResponse; 14 | import java.io.IOException; 15 | import java.io.PrintWriter; 16 | 17 | /** 18 | * @author ngcly 19 | * @date 2018/3/24 下午7:43 20 | * 登录失败处理 21 | */ 22 | public class LoginFailureHandler implements AuthenticationFailureHandler { 23 | @Override 24 | public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, 25 | AuthenticationException exception) throws IOException { 26 | response.setContentType(ContentType.APPLICATION_JSON.toString()); 27 | RestCode restCode; 28 | if (exception instanceof BadCredentialsException || 29 | exception instanceof UsernameNotFoundException) { 30 | restCode = RestCode.USER_ERR; 31 | } else if (exception instanceof AuthenticationServiceException) { 32 | restCode = RestCode.VERIFY_CODE_ERR; 33 | } else if (exception instanceof LockedException) { 34 | restCode = RestCode.USER_LOCKED; 35 | } else if (exception instanceof AccountExpiredException) { 36 | restCode = RestCode.USER_EXPIRE; 37 | } else if (exception instanceof CredentialsExpiredException) { 38 | restCode = RestCode.PASSWORD_EXPIRE; 39 | } else if (exception instanceof DisabledException) { 40 | restCode = RestCode.USER_DISABLE; 41 | } else { 42 | restCode = RestCode.UNAUTHORIZED; 43 | } 44 | try (PrintWriter printWriter = response.getWriter()) { 45 | String jsonStr = JacksonUtil.stringify(Result.failure(restCode)); 46 | printWriter.write(jsonStr); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /backstage/src/main/java/com/cn/config/LoginSuccessHandler.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import com.cn.util.JacksonUtil; 4 | import com.cn.util.Result; 5 | import org.apache.http.entity.ContentType; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 8 | 9 | import jakarta.servlet.http.HttpServletRequest; 10 | import jakarta.servlet.http.HttpServletResponse; 11 | import java.io.IOException; 12 | import java.io.PrintWriter; 13 | 14 | /** 15 | * 登录成功后事件 16 | * 17 | * @author ngcly 18 | * @since 2018-01-02 18:53 19 | */ 20 | public class LoginSuccessHandler implements AuthenticationSuccessHandler { 21 | 22 | @Override 23 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, 24 | Authentication authentication) throws IOException { 25 | 26 | response.setContentType(ContentType.APPLICATION_JSON.toString()); 27 | try (PrintWriter printWriter = response.getWriter()) { 28 | String jsonStr = JacksonUtil.stringify(Result.success()); 29 | printWriter.write(jsonStr); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /backstage/src/main/java/com/cn/config/MyAuthenticationDetailsSource.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import com.cn.model.AuthenticationDetails; 4 | import org.springframework.security.authentication.AuthenticationDetailsSource; 5 | import org.springframework.stereotype.Component; 6 | 7 | import jakarta.servlet.http.HttpServletRequest; 8 | 9 | /** 10 | * @author ngcly 11 | */ 12 | @Component 13 | public class MyAuthenticationDetailsSource implements AuthenticationDetailsSource { 14 | @Override 15 | public AuthenticationDetails buildDetails(HttpServletRequest context) { 16 | return new AuthenticationDetails(context); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /backstage/src/main/java/com/cn/config/MyAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import cn.hutool.core.text.CharSequenceUtil; 4 | import com.cn.model.AuthenticationDetails; 5 | import com.cn.model.CaptchaInfo; 6 | import org.springframework.security.authentication.AuthenticationServiceException; 7 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 8 | import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 9 | import org.springframework.security.core.AuthenticationException; 10 | import org.springframework.security.core.userdetails.UserDetails; 11 | 12 | import java.util.Objects; 13 | 14 | /** 15 | * @author chenning 16 | */ 17 | public class MyAuthenticationProvider extends DaoAuthenticationProvider { 18 | 19 | @Override 20 | protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { 21 | //获取详细信息 22 | AuthenticationDetails details = (AuthenticationDetails) authentication.getDetails(); 23 | 24 | String userCaptcha = details.getUserCaptcha(); 25 | CaptchaInfo captchaInfo = details.getCaptchaInfo(); 26 | if (Objects.isNull(captchaInfo) || captchaInfo.isExpire() 27 | || !CharSequenceUtil.equalsIgnoreCase(captchaInfo.code(), userCaptcha)) { 28 | throw new AuthenticationServiceException("Captcha error!"); 29 | } 30 | super.additionalAuthenticationChecks(userDetails, authentication); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /backstage/src/main/java/com/cn/config/MyAuthorizationManager.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import com.cn.ManagerService; 4 | import com.cn.entity.Manager; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.security.authorization.AuthorizationDecision; 7 | import org.springframework.security.authorization.AuthorizationManager; 8 | import org.springframework.security.core.Authentication; 9 | import org.springframework.security.core.GrantedAuthority; 10 | import org.springframework.security.web.access.intercept.RequestAuthorizationContext; 11 | 12 | import java.util.Collection; 13 | import java.util.List; 14 | import java.util.function.Supplier; 15 | 16 | /** 17 | * @author ngcly 18 | */ 19 | @RequiredArgsConstructor 20 | public class MyAuthorizationManager implements AuthorizationManager { 21 | private final ManagerService managerService; 22 | 23 | @Override 24 | public AuthorizationDecision check(Supplier authentication, RequestAuthorizationContext context) { 25 | //当前用户名 26 | String username = authentication.get().getName(); 27 | //admin账号 就没有过不去的坎 28 | if(Manager.ADMIN.equals(username)) { 29 | return new AuthorizationDecision(true); 30 | } 31 | 32 | //当前用户权限信息 33 | Collection authorities = authentication.get().getAuthorities(); 34 | 35 | //获取当前url 36 | String currentUrl = context.getRequest().getServletPath(); 37 | String requestMethod = context.getRequest().getMethod(); 38 | 39 | String currentUrlKey = String.join("_", requestMethod, currentUrl); 40 | 41 | //url权限 元数据 42 | List permissionMetadata = managerService.getUrlPermissionMetadata(); 43 | 44 | //是否为需要鉴权url 45 | boolean isAuthenticationUrl = permissionMetadata.contains(currentUrlKey); 46 | //非鉴权url 直接通过 47 | if(!isAuthenticationUrl) { 48 | return new AuthorizationDecision(true); 49 | } 50 | 51 | boolean isGranted = authorities.stream().anyMatch(grantedAuthority -> 52 | currentUrlKey.equals(grantedAuthority.getAuthority())); 53 | 54 | return new AuthorizationDecision(isGranted); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /backstage/src/main/java/com/cn/controller/IndexController.java: -------------------------------------------------------------------------------- 1 | package com.cn.controller; 2 | 3 | import cn.hutool.captcha.CaptchaUtil; 4 | import cn.hutool.captcha.ICaptcha; 5 | import com.cn.ManagerService; 6 | import com.cn.entity.Manager; 7 | import com.cn.entity.Role; 8 | import com.cn.enums.ResourceEnum; 9 | import com.cn.enums.UserStatusEnum; 10 | import com.cn.model.MenuDTO; 11 | import com.cn.model.CaptchaInfo; 12 | import com.cn.util.MenuUtil; 13 | import lombok.AllArgsConstructor; 14 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 15 | import org.springframework.stereotype.Controller; 16 | import org.springframework.ui.Model; 17 | import org.springframework.web.bind.annotation.GetMapping; 18 | import org.springframework.web.bind.annotation.RequestMapping; 19 | import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; 20 | 21 | import jakarta.servlet.http.HttpServletRequest; 22 | 23 | import java.util.*; 24 | 25 | /** 26 | * 后台首页控制器 27 | * 28 | * @author ngcly 29 | * @since 2018-01-02 17:26 30 | */ 31 | @Controller 32 | @AllArgsConstructor 33 | public class IndexController { 34 | private final ManagerService managerService; 35 | 36 | /** 37 | * 首页控制台 38 | */ 39 | @RequestMapping("/") 40 | public String index(@AuthenticationPrincipal Manager manager, Model model) { 41 | boolean init = false; 42 | Collection roleList; 43 | if (Manager.ADMIN.equals(manager.getUsername())) { 44 | roleList = managerService.getAdministrator().getRoleList(); 45 | } else { 46 | Manager dbManager = managerService.getManagerById(manager.getId()); 47 | roleList = dbManager.getRoleList(); 48 | if (dbManager.getState() == UserStatusEnum.INITIALIZE) { 49 | init = true; 50 | dbManager.setState(UserStatusEnum.NORMAL); 51 | managerService.updateManager(dbManager); 52 | } 53 | } 54 | List menuList = roleList.parallelStream() 55 | .map(Role::getPermissions) 56 | .flatMap(Collection::parallelStream) 57 | .filter(permission -> ResourceEnum.MENU == permission.getResourceType()) 58 | .distinct() 59 | .map(MenuDTO.class::cast) 60 | .toList(); 61 | 62 | model.addAttribute("init", init); 63 | model.addAttribute("manager", manager); 64 | model.addAttribute("menuList", MenuUtil.makeMenuToTree(menuList)); 65 | return "index"; 66 | } 67 | 68 | /** 69 | * 登录页面 70 | * 71 | * @return login.html 72 | */ 73 | @RequestMapping("/login") 74 | public String login() { 75 | return "login"; 76 | } 77 | 78 | /** 79 | * 验证码 80 | * 81 | * @param request 请求对象 82 | */ 83 | @GetMapping("/captcha") 84 | public StreamingResponseBody generateCaptcha(HttpServletRequest request) { 85 | ICaptcha captcha = CaptchaUtil.createGifCaptcha(116, 36, 4); 86 | CaptchaInfo captchaInfo = new CaptchaInfo(captcha.getCode(), 60); 87 | request.getSession().setAttribute("captcha", captchaInfo); 88 | return captcha::write; 89 | } 90 | 91 | /** 92 | * 主页 93 | */ 94 | @RequestMapping("/home") 95 | public String home() { 96 | return "home"; 97 | } 98 | 99 | /** 100 | * 云中遨游 101 | */ 102 | @RequestMapping("/clouds") 103 | public String clouds() { 104 | return "other/clouds"; 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /backstage/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9090 3 | servlet: 4 | session: 5 | timeout: 1800 6 | 7 | spring: 8 | jpa: 9 | database: mysql 10 | show-sql: true 11 | generate-ddl: true 12 | hibernate: 13 | ddl-auto: update 14 | datasource: 15 | url: jdbc:mysql://localhost:3306/music_story?serverTimezone=UTC&characterEncoding=utf-8&allowPublicKeyRetrieval=true 16 | username: root 17 | password: 12345678 18 | driverClassName: com.mysql.cj.jdbc.Driver 19 | 20 | docker: 21 | compose: 22 | enabled: true 23 | file: docker-compose.yml -------------------------------------------------------------------------------- /backstage/src/main/resources/application-local.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9090 3 | servlet: 4 | session: 5 | timeout: 1800 6 | 7 | spring: 8 | jpa: 9 | show-sql: true 10 | hibernate: 11 | ddl-auto: update 12 | defer-datasource-initialization: true 13 | datasource: 14 | url: jdbc:h2:mem:testdb 15 | username: sa 16 | password: 17 | driverClassName: org.h2.Driver 18 | h2: 19 | console: 20 | enabled: true 21 | docker: 22 | compose: 23 | enabled: true 24 | file: docker-compose.yml -------------------------------------------------------------------------------- /backstage/src/main/resources/application-pro.yml: -------------------------------------------------------------------------------- 1 | #正式环境配置文件 2 | server: 3 | port: 9090 4 | 5 | #数据源 6 | spring: 7 | jpa: 8 | database: mysql 9 | show-sql: false 10 | generate-ddl: false 11 | hibernate: 12 | ddl-auto: update 13 | datasource: 14 | url: jdbc:mysql://127.0.0.1:3306/music_story?characterEncoding=utf-8 15 | username: root 16 | password: ENC(DkDbcuxOYpRIr7dbhXyi4ZdyL0q+Rm4UAhWC/wzqSs7gz7EImE5goaDzHWGGXXL7) 17 | driverClassName: com.mysql.cj.jdbc.Driver 18 | -------------------------------------------------------------------------------- /backstage/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: local 4 | 5 | jackson: 6 | date-format: 'yyyy-MM-dd HH:mm:ss' 7 | time-zone: 'GMT+8' 8 | 9 | mail: 10 | host: smtp.qq.com 11 | port: 465 12 | username: 531237716@qq.com 13 | password: ENC(pS7NvCDk4ouixF9t8kcIPfrRUr/ZZ3J+JGBYc5yT4TAGHb8YBLu7r3l2ZSWoPuFIy7ROpcPHZCP/gYBTHWpNvw==) 14 | properties: 15 | mail: 16 | smtp: 17 | auth: true 18 | starttls: 19 | enable: true 20 | required: true 21 | ssl: 22 | enable: true 23 | 24 | thymeleaf: 25 | cache: false 26 | encoding: UTF-8 27 | enable-spring-el-compiler: true 28 | 29 | cache: 30 | type: redis 31 | 32 | # redis: 33 | # #集群模式 34 | # cluster: 35 | # nodes: 192.168.177.128:7001,192.168.177.128:7002,192.168.177.128:7003,192.168.177.128:7004,192.168.177.128:7005,192.168.177.128:7006 36 | # max-redirects: 3 37 | # #哨兵模式 38 | # sentinel: 39 | # master: mymaster 40 | # nodes: 127.0.0.1:26379,127.0.0.1:26479 41 | 42 | datasource: 43 | type: com.alibaba.druid.pool.DruidDataSource 44 | druid: 45 | initial-size: 5 46 | min-idle: 5 47 | max-active: 20 48 | max-wait: 60000 49 | validation-query: SELECT 1 50 | time-between-eviction-runs-millis: 60000 51 | min-evictable-idle-time-millis: 30000 52 | pool-prepared-statements: true 53 | max-open-prepared-statements: 20 54 | filter: 55 | stat: 56 | enabled: true 57 | log-slow-sql: true 58 | slow-sql-millis: 1000 59 | merge-sql: true 60 | # wall: 61 | # enabled: true 62 | web-stat-filter: 63 | enabled: true 64 | url-pattern: '/*' 65 | exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' 66 | profile-enable: true 67 | session-stat-enable: true 68 | session-stat-max-count: 100000 69 | aop-patterns: 'com.cn.*' 70 | stat-view-servlet: 71 | enabled: true 72 | url-pattern: '/druid/*' 73 | reset-enable: true 74 | # login-username: 123456 75 | # login-password: admin 76 | 77 | 78 | oss: 79 | config: 80 | host: https://oss-cn-hongkong-internal.aliyuncs.com 81 | bucket-name: music-story 82 | access-key-id: ENC(BFmGqMfhERt8BVG81BuQFAI4L165TxyjahwvlTGx9QnjkKAWytNvhhtfiajw1mhsGTLh5JErMOFrjltogIkOUg==) 83 | access-key-secret: ENC(Ng+yTJPV6RPseJCbuVbk2vqwElnl5fdddl22TAYCvyOVoHSjINi9Tk01j+8CLmozS/ysp3gWFbU5Ock4znKENw==) 84 | endpoint: oss.ngcly.cn 85 | -------------------------------------------------------------------------------- /backstage/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ## ## ## ## ###### #### ###### 2 | ### ### ## ## ## ## ## ## ## 3 | #### #### ## ## ## ## ## 4 | ## ### ## ## ## ###### ## ## 5 | ## ## ## ## ## ## ## 6 | ## ## ## ## ## ## ## ## ## 7 | ## ## ####### ###### #### ###### 8 | ###### ######## ####### ######## ## ## 9 | ## ## ## ## ## ## ## ## ## 10 | ## ## ## ## ## ## #### 11 | ###### ## ## ## ######## ## 12 | ## ## ## ## ## ## ## 13 | ## ## ## ## ## ## ## ## 14 | ###### ## ####### ## ## ## -------------------------------------------------------------------------------- /backstage/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ${APP_NAME} 10 | 11 | 12 | 13 | ${LOG_FILE_PATH}/${APP_NAME}-%d{yyyy-MM-dd}.log 14 | 30 15 | 16 | 17 | ${FILE_LOG_PATTERN} 18 | 19 | 20 | 21 | 22 | 23 | localhost:4560 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /backstage/src/main/resources/static/css/login.css: -------------------------------------------------------------------------------- 1 | body{overflow:hidden;} 2 | 3 | .video-player{background-color: transparent;min-width: 100%;min-height: 100%;display: block;position: absolute;z-index: 1;top:0;} 4 | .video_mask{ width:100%; height:100%; position:absolute; left:0; top:0; z-index:90; background-color:rgba(0,0,0,0.5); } 5 | .login{ height:260px;width:260px;padding: 20px;background-color:rgba(0,0,0,0.5);border-radius: 4px;position:absolute;left: 50%;top: 50%; margin:-150px 0 0 -150px;z-index:99;} 6 | .login h1{ text-align:center; color:#fff; font-size:24px; margin-bottom:20px; } 7 | .form_code{ position:relative; } 8 | .form_code .code{ position:absolute; right:0; top:1px; cursor:pointer; } 9 | .login_btn{ width:50%; margin-right: 10px} -------------------------------------------------------------------------------- /backstage/src/main/resources/static/css/main.css: -------------------------------------------------------------------------------- 1 | /*引入阿里个人图标库*/ 2 | @import "https://at.alicdn.com/t/font_594558_ajc56eo86iw45cdi.css"; 3 | .showMenu.layui-layout-admin .layui-side{ left:-200px; } 4 | .showMenu .layui-body,.showMenu .layui-footer{ left:0; } 5 | /*.hideMenu{ float:left; width:20px; height:20px; margin-top:20px; font-size:17px; line-height:20px; text-align:center; color:#fff; background-color:#1AA094; }*/ 6 | .mini .layui-side-scroll ul li cite { display: none; } 7 | .mini .layui-nav-tree .layui-nav-more { right: 3px;} 8 | .mini .layui-nav-tree {width: 50px;} 9 | /*下面是菜单使用了个人图标情况下的样式申明 否则mini状态不显示图标*/ 10 | /*.mini .layui-nav-tree .layui-nav-item a {text-overflow: clip !important;}*/ 11 | /*样式改变的过渡*/ 12 | .layui-body,.layui-footer,.layui-layout-admin .layui-side{ -o-transition: all 0.3s ease-in-out;-moz-transition: all 0.3s ease-in-out;-ms-transition: all 0.3s ease-in-out; } 13 | iframe{ position:absolute; height:100%; width:100%; border:none;} 14 | /*iframe 其他属性 frameborder="false" scrolling="auto" style=allowtransparency="true"*/ 15 | 16 | .layui-nav cite{ margin-left: 5px;} 17 | -------------------------------------------------------------------------------- /backstage/src/main/resources/static/css/metroStyle/img/line_conn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngcly/MusicStory/183675ed994f9037033e8406bb3999c4b43aa2a9/backstage/src/main/resources/static/css/metroStyle/img/line_conn.png -------------------------------------------------------------------------------- /backstage/src/main/resources/static/css/metroStyle/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngcly/MusicStory/183675ed994f9037033e8406bb3999c4b43aa2a9/backstage/src/main/resources/static/css/metroStyle/img/loading.gif -------------------------------------------------------------------------------- /backstage/src/main/resources/static/css/metroStyle/img/metro.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngcly/MusicStory/183675ed994f9037033e8406bb3999c4b43aa2a9/backstage/src/main/resources/static/css/metroStyle/img/metro.gif -------------------------------------------------------------------------------- /backstage/src/main/resources/static/css/metroStyle/img/metro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngcly/MusicStory/183675ed994f9037033e8406bb3999c4b43aa2a9/backstage/src/main/resources/static/css/metroStyle/img/metro.png -------------------------------------------------------------------------------- /backstage/src/main/resources/static/css/public.css: -------------------------------------------------------------------------------- 1 | @import "https://at.alicdn.com/t/font_594558_g140ya4hgdlpu8fr.css"; 2 | .child-body{ padding: 10px 15px 15px 15px } 3 | .child-nav{ position: relative;border-bottom: 1px solid #e5e5e5;overflow: hidden;height: 39px;line-height: 39px;margin-bottom: 15px} 4 | .search-row{ padding: 7px 0px;background-color:#f2f2f2 } 5 | .search-label{ float:left;display: block;text-align: right;padding: 9px 5px;font-weight: 400;line-height: 20px} 6 | .search-input{ float:left;display: inline-block;vertical-align: middle;width: 110px;margin-right: 2px} 7 | .date-input{ float:left;display: inline-block;vertical-align: middle;width: 150px;margin-right: 2px} 8 | 9 | .treegrid-indent{width:16px;height:16px;display:inline-block;position:relative}.treegrid-expander{width:16px;height:16px;display:inline-block;position:relative;cursor:pointer} 10 | .treeview .list-group-item{cursor:pointer}.treeview span.indent{margin-left:10px;margin-right:10px}.treeview span.icon{width:12px;margin-right:5px}.treeview .node-disabled{color:silver;cursor:not-allowed} -------------------------------------------------------------------------------- /backstage/src/main/resources/static/images/cloud10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngcly/MusicStory/183675ed994f9037033e8406bb3999c4b43aa2a9/backstage/src/main/resources/static/images/cloud10.png -------------------------------------------------------------------------------- /backstage/src/main/resources/static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngcly/MusicStory/183675ed994f9037033e8406bb3999c4b43aa2a9/backstage/src/main/resources/static/images/favicon.ico -------------------------------------------------------------------------------- /backstage/src/main/resources/static/images/wx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngcly/MusicStory/183675ed994f9037033e8406bb3999c4b43aa2a9/backstage/src/main/resources/static/images/wx.jpg -------------------------------------------------------------------------------- /backstage/src/main/resources/static/images/zfb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngcly/MusicStory/183675ed994f9037033e8406bb3999c4b43aa2a9/backstage/src/main/resources/static/images/zfb.jpg -------------------------------------------------------------------------------- /backstage/src/main/resources/static/js/Detector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * @author mr.doob / http://mrdoob.com/ 4 | */ 5 | const Detector = { 6 | 7 | canvas: !!window.CanvasRenderingContext2D, 8 | webgl: (function () { 9 | try { 10 | return !!window.WebGLRenderingContext && !!document.createElement('canvas').getContext('experimental-webgl'); 11 | } catch (e) { 12 | return false; 13 | } 14 | })(), 15 | workers: !!window.Worker, 16 | fileapi: window.File && window.FileReader && window.FileList && window.Blob, 17 | 18 | getWebGLErrorMessage: function () { 19 | 20 | let domElement = document.createElement('div'); 21 | 22 | domElement.style.fontFamily = 'monospace'; 23 | domElement.style.fontSize = '13px'; 24 | domElement.style.textAlign = 'center'; 25 | domElement.style.background = '#eee'; 26 | domElement.style.color = '#000'; 27 | domElement.style.padding = '1em'; 28 | domElement.style.width = '475px'; 29 | domElement.style.margin = '5em auto 0'; 30 | 31 | if (!this.webgl) { 32 | 33 | domElement.innerHTML = window.WebGLRenderingContext ? [ 34 | 'Sorry, your graphics card doesn\'t support WebGL' 35 | ].join('\n') : [ 36 | 'Sorry, your browser doesn\'t support WebGL
', 37 | 'Please try with', 38 | 'Chrome 10, ', 39 | 'Firefox 4 or', 40 | 'Safari 6' 41 | ].join('\n'); 42 | 43 | } 44 | 45 | return domElement; 46 | 47 | }, 48 | 49 | addGetWebGLMessage: function (parameters) { 50 | 51 | let parent, id, domElement; 52 | 53 | parameters = parameters || {}; 54 | 55 | parent = parameters.parent !== undefined ? parameters.parent : document.body; 56 | id = parameters.id !== undefined ? parameters.id : 'oldie'; 57 | 58 | domElement = Detector.getWebGLErrorMessage(); 59 | domElement.id = id; 60 | 61 | parent.appendChild(domElement); 62 | 63 | } 64 | 65 | }; 66 | -------------------------------------------------------------------------------- /backstage/src/main/resources/static/js/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by cly on 2017/12/21 0027. 3 | * 设置iframe高度 4 | */ 5 | var indexModel = (function () { 6 | return { 7 | initFrameHeight : function () { 8 | var window_height = $(window).height(); 9 | var header_height = $('.layui-header').outerHeight(); 10 | var footer_height = $('.layui-footer').outerHeight(); 11 | $("#content").height(window_height - header_height - footer_height-10); 12 | } 13 | 14 | } 15 | })(); 16 | 17 | (function () { 18 | indexModel.initFrameHeight(); 19 | })(); -------------------------------------------------------------------------------- /backstage/src/main/resources/static/media/login.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngcly/MusicStory/183675ed994f9037033e8406bb3999c4b43aa2a9/backstage/src/main/resources/static/media/login.mp4 -------------------------------------------------------------------------------- /backstage/src/main/resources/static/media/login.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngcly/MusicStory/183675ed994f9037033e8406bb3999c4b43aa2a9/backstage/src/main/resources/static/media/login.webm -------------------------------------------------------------------------------- /backstage/src/main/resources/templates/carousel/carouselCategoryEdit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 轮播图分类编辑页 6 | 7 | 8 | 9 | 10 | 11 |

12 |
13 |
14 | 15 |
16 | 17 |
18 |
19 |
20 | 21 |
22 | 23 |
24 |
25 |
26 | 27 |
28 | 29 |
30 |
31 |
32 |
33 | 34 | 35 |
36 |
37 |
38 |
39 | 40 | 41 | 81 | 82 | -------------------------------------------------------------------------------- /backstage/src/main/resources/templates/classify/classifyEdit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 新增修改分类页 6 | 7 | 8 | 9 |
10 |
11 |
12 |
13 |
14 | 15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 | 23 |
24 | 25 |
26 |
27 |
28 |
29 | 30 | 31 |
32 |
33 |
34 |
35 |
36 |
37 | 38 | 80 | 81 | -------------------------------------------------------------------------------- /backstage/src/main/resources/templates/error/403.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 音书 6 | 7 | 8 | 9 | 10 |
11 | 12 |

抱歉,该页面属于机密!

13 |
14 | 15 | -------------------------------------------------------------------------------- /backstage/src/main/resources/templates/error/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 页面丢了 6 | 7 | 8 | 9 | 10 |
11 | 12 |

我勒个去,页面被外星人挟持了!

13 |
14 | 15 | -------------------------------------------------------------------------------- /backstage/src/main/resources/templates/error/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 音书 6 | 7 | 8 | 9 | 10 |
11 | 12 |

糟糕,服务器好像出差了!

13 |
14 | 15 | -------------------------------------------------------------------------------- /backstage/src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 登录 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 |
21 |
22 |

后台管理登录

23 |
24 |
25 | 26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 | 36 |
37 |
38 | 39 | 79 | 80 | -------------------------------------------------------------------------------- /backstage/src/main/resources/templates/manager/managerDetail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 管理员详情页 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
用户名:真实姓名:
性别:状态: 23 | 初始化 24 | 正常 25 | 封禁 26 |
生日:头像:
创建时间:修改时间:
角色:
46 |
47 | 48 | 58 | 59 | -------------------------------------------------------------------------------- /backstage/src/main/resources/templates/manager/updatePwd.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 修改密码 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 |
25 | 26 |
27 |
6到16个字符
28 |
29 |
30 | 31 |
32 | 33 |
34 |
35 |
36 |
37 | 38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 | 89 | 90 | -------------------------------------------------------------------------------- /backstage/src/main/resources/templates/role/grant.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 授权窗口 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 |
15 | 16 |
17 |
18 |
19 | 20 | 21 |
22 |
23 | 24 | 25 | 26 | 27 | 76 | 77 | -------------------------------------------------------------------------------- /backstage/src/main/resources/templates/user/userDetail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 用户详情页 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
用户名:昵称:
真实姓名:个性签名:
性别:状态: 29 | 禁用 30 | 正常 31 | 异常 32 |
手机:邮箱:
生日:头像:
等级:积分:
创建时间:修改时间:
地址:简介:
余额:角色:
72 |
73 | 74 | 84 | 85 | -------------------------------------------------------------------------------- /backstage/src/test/java/com/cn/BackstageApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.cn; 2 | 3 | import com.cn.enums.ConfigEnum; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | 8 | /** 9 | * 测试类 10 | * 11 | * @author ngcly 12 | * @date 2018-03-15 12:46 13 | */ 14 | @SpringBootTest 15 | class BackstageApplicationTests { 16 | 17 | @BeforeAll 18 | static void setup(){ 19 | ConfigEnum configEnum = ConfigEnum.JASYPT_ENCRYPTOR; 20 | System.setProperty(configEnum.getKey(),configEnum.getValue()); 21 | } 22 | 23 | @Test 24 | void contextLoads() { 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backstage/src/test/java/com/cn/controller/IndexControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.cn.controller; 2 | 3 | import cn.hutool.captcha.CaptchaUtil; 4 | import cn.hutool.captcha.ICaptcha; 5 | import cn.hutool.core.date.DateField; 6 | import cn.hutool.core.date.DateUtil; 7 | import cn.hutool.core.lang.Assert; 8 | import cn.hutool.crypto.SecureUtil; 9 | import cn.hutool.jwt.JWT; 10 | import cn.hutool.jwt.JWTUtil; 11 | import org.junit.jupiter.api.Assertions; 12 | import org.junit.jupiter.api.Test; 13 | 14 | import java.io.*; 15 | import java.time.LocalDateTime; 16 | import java.util.Date; 17 | import java.util.Scanner; 18 | 19 | /** 20 | * 21 | * @author ngcly 22 | * @version V1.0 23 | * @since 2021/8/14 18:48 24 | */ 25 | class IndexControllerTest { 26 | 27 | @Test 28 | void defaultCaptcha() throws IOException { 29 | ICaptcha captcha = CaptchaUtil.createGifCaptcha(116, 36, 4); 30 | try (FileOutputStream out = new FileOutputStream("d:/gif.gif")){ 31 | captcha.write(out); 32 | } 33 | Assert.isTrue(captcha.verify(captcha.getCode())); 34 | } 35 | 36 | @Test 37 | void jwtTest() { 38 | final Date now =new Date(); 39 | byte[] secret = SecureUtil.md5("ssss").getBytes(); 40 | String token = JWT.create() 41 | .setSubject("cly") 42 | .setIssuedAt(now) 43 | .setExpiresAt(DateUtil.offset(now, DateField.SECOND,3)) 44 | .setKey(secret) 45 | .sign(); 46 | System.out.println(token); 47 | JWT jwt = JWTUtil.parseToken(token); 48 | Assert.isTrue(JWT.of(token).setKey(secret).validate(0L)); 49 | String username = jwt.getPayloads().get(JWT.SUBJECT,String.class); 50 | Date date = jwt.getPayloads().getDate(JWT.ISSUED_AT); 51 | LocalDateTime issuedAt = DateUtil.toLocalDateTime(date); 52 | System.out.println(username); 53 | System.out.println(issuedAt); 54 | } 55 | 56 | @Test 57 | void test() throws IOException { 58 | Runtime runtime = Runtime.getRuntime(); 59 | System.out.println(runtime.availableProcessors()); 60 | System.out.println(runtime.maxMemory()); 61 | Scanner scanner = new Scanner(new ByteArrayInputStream("ssss".getBytes())); 62 | 63 | ByteArrayOutputStream writer = new ByteArrayOutputStream(); 64 | PrintStream printStream = new PrintStream(new ByteArrayOutputStream()); 65 | Assertions.assertNotNull(scanner); 66 | if(scanner.hasNext()){ 67 | String s= scanner.next(); 68 | writer.write(s.getBytes()); 69 | printStream.print(s); 70 | } 71 | System.out.println(writer); 72 | System.out.println(printStream); 73 | 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.cn.cly' 2 | version '1.0-SNAPSHOT' 3 | 4 | //当前根目录配置优先级高于子模块配置 如果此处配置与子模块配置有一致部分 则会覆盖子模块 5 | 6 | buildscript { //构建脚本 7 | //定义版本号 8 | ext { 9 | springBootVersion = '3.4.0' 10 | } 11 | repositories {//jar包仓库 12 | maven { url 'https://maven.aliyun.com/repository/central' } 13 | maven { url "https://maven.aliyun.com/repository/gradle-plugin" } 14 | mavenCentral() 15 | } 16 | dependencies { 17 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 18 | } 19 | } 20 | 21 | subprojects { //子项目通配 22 | apply plugin: 'java' 23 | apply plugin: 'idea' 24 | apply plugin: 'java-library' 25 | apply plugin: 'org.springframework.boot' 26 | apply plugin: 'io.spring.dependency-management' 27 | 28 | //编译使用UTF-8 29 | [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' 30 | 31 | sourceCompatibility = JavaVersion.VERSION_17 32 | targetCompatibility = JavaVersion.VERSION_17 33 | 34 | configurations { 35 | compileOnly { 36 | extendsFrom annotationProcessor 37 | } 38 | } 39 | 40 | repositories { //所有子项目jar包中央仓库 41 | maven { url 'https://maven.aliyun.com/repository/central' } 42 | maven { url "https://maven.aliyun.com/repository/gradle-plugin" } 43 | mavenCentral() 44 | } 45 | 46 | //定义版本 47 | ext { 48 | jasyptVersion = '3.0.5' 49 | docVersion = '2.5.0' 50 | jsoupVersion = '1.14.3' 51 | nekohtmlVersion = '1.9.22' 52 | hutoolVersion = '5.8.28' 53 | guavaVersion = '33.2.1-jre' 54 | aliOssVersion = '3.17.4' 55 | druidVersion = '1.2.23' 56 | dialectVersion = '3.2.0' 57 | locatorVersion = '1.0.1' 58 | jqueryVersion = '3.7.1' 59 | layuiVersion = '2.9.18' 60 | treegridVersion = '0.3.0' 61 | logstashVersion = '7.4' 62 | jacksonVersion = '2.13.3' 63 | } 64 | 65 | dependencies { //所有子项目通用依赖 66 | implementation 'org.springframework.boot:spring-boot-starter-actuator' 67 | implementation 'org.springframework.boot:spring-boot-starter-web' 68 | implementation 'org.springframework.boot:spring-boot-starter-security' 69 | implementation 'org.springframework.boot:spring-boot-starter-validation' 70 | implementation "com.github.ulisesbocchio:jasypt-spring-boot-starter:${jasyptVersion}" 71 | implementation "net.logstash.logback:logstash-logback-encoder:${logstashVersion}" 72 | 73 | compileOnly 'org.projectlombok:lombok' 74 | developmentOnly 'org.springframework.boot:spring-boot-devtools' 75 | developmentOnly 'org.springframework.boot:spring-boot-docker-compose' 76 | annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' 77 | annotationProcessor 'org.projectlombok:lombok' 78 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 79 | testImplementation 'org.springframework.security:spring-security-test' 80 | testRuntimeOnly 'org.junit.platform:junit-platform-launcher' 81 | } 82 | 83 | tasks.named('test') { 84 | useJUnitPlatform() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /commons/build.gradle: -------------------------------------------------------------------------------- 1 | jar { 2 | enabled = true 3 | } 4 | bootJar { 5 | enabled = false 6 | } 7 | 8 | dependencies { 9 | api 'org.springframework.boot:spring-boot-starter-data-redis' 10 | api 'org.springframework.boot:spring-boot-starter-amqp' 11 | api 'org.springframework.boot:spring-boot-starter-websocket' 12 | api "org.springdoc:springdoc-openapi-starter-webmvc-ui:${docVersion}" 13 | api "com.google.guava:guava:${guavaVersion}" 14 | api "cn.hutool:hutool-core:${hutoolVersion}" 15 | api "cn.hutool:hutool-extra:${hutoolVersion}" 16 | api "cn.hutool:hutool-http:${hutoolVersion}" 17 | api "cn.hutool:hutool-captcha:${hutoolVersion}" 18 | api "cn.hutool:hutool-jwt:${hutoolVersion}" 19 | implementation "com.aliyun.oss:aliyun-sdk-oss:${aliOssVersion}" 20 | implementation 'org.springframework.boot:spring-boot-starter-mail' 21 | } 22 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/config/AsyncPoolConfig.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.scheduling.annotation.AsyncConfigurer; 8 | import org.springframework.scheduling.annotation.EnableAsync; 9 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 10 | import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; 11 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 12 | 13 | import java.util.concurrent.Executor; 14 | import java.util.concurrent.ThreadPoolExecutor; 15 | 16 | /** 17 | * @author ngcly 18 | */ 19 | @Slf4j 20 | @EnableAsync 21 | @Configuration 22 | public class AsyncPoolConfig implements AsyncConfigurer, WebMvcConfigurer { 23 | 24 | @Bean 25 | public ThreadPoolTaskExecutor asyncExecutor() { 26 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 27 | executor.setCorePoolSize(10); 28 | executor.setMaxPoolSize(50); 29 | executor.setQueueCapacity(10); 30 | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 31 | executor.setWaitForTasksToCompleteOnShutdown(true); 32 | executor.setAwaitTerminationSeconds(5); 33 | executor.initialize(); 34 | return executor; 35 | } 36 | 37 | 38 | @Override 39 | public void configureAsyncSupport(AsyncSupportConfigurer configurer) { 40 | configurer.setTaskExecutor(asyncExecutor()); 41 | } 42 | 43 | @Override 44 | public Executor getAsyncExecutor() { 45 | return asyncExecutor(); 46 | } 47 | 48 | @Override 49 | public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { 50 | return (ex, method, params) -> log.error(String.format("执行异步任务'%s'", method), ex); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/config/JacksonConfig.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import com.fasterxml.jackson.databind.*; 4 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 5 | import com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer; 6 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; 7 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; 8 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; 9 | import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | 14 | import java.time.*; 15 | import java.time.format.DateTimeFormatter; 16 | 17 | /** 18 | * jackson 序列化与反序列化 时间类型 19 | * @author ngcly 20 | */ 21 | //@Configuration 22 | //public class JacksonConfig { 23 | // 24 | // @Value("${spring.jackson.date-format}") 25 | // private String pattern; 26 | // 27 | // @Bean 28 | // public ObjectMapper serializingObjectMapper() { 29 | // JavaTimeModule javaTimeModule = new JavaTimeModule(); 30 | // javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern))); 31 | // javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE)); 32 | // javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME)); 33 | // javaTimeModule.addSerializer(Instant.class, InstantSerializer.INSTANCE); 34 | // 35 | // return new ObjectMapper() 36 | // .registerModule(new ParameterNamesModule()) 37 | // .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) 38 | // .registerModule(javaTimeModule); 39 | // } 40 | // 41 | //} 42 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/config/OssAutoConfig.java: -------------------------------------------------------------------------------- 1 | //package com.cn.config; 2 | // 3 | //import com.amazonaws.ClientConfiguration; 4 | //import com.amazonaws.auth.AWSCredentials; 5 | //import com.amazonaws.auth.AWSCredentialsProvider; 6 | //import com.amazonaws.auth.AWSStaticCredentialsProvider; 7 | //import com.amazonaws.auth.BasicAWSCredentials; 8 | //import com.amazonaws.client.builder.AwsClientBuilder; 9 | //import com.amazonaws.services.s3.AmazonS3; 10 | //import com.amazonaws.services.s3.AmazonS3Client; 11 | //import lombok.RequiredArgsConstructor; 12 | //import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 13 | //import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 14 | //import org.springframework.boot.context.properties.EnableConfigurationProperties; 15 | //import org.springframework.context.annotation.Bean; 16 | //import org.springframework.context.annotation.Configuration; 17 | // 18 | ///** 19 | // * @author chenning 20 | // */ 21 | //@Configuration 22 | //@RequiredArgsConstructor 23 | //@EnableConfigurationProperties(OssProperties.class) 24 | //public class OssAutoConfig { 25 | // @Bean 26 | // @ConditionalOnMissingBean 27 | // public AmazonS3 ossClient(OssProperties ossProperties) { 28 | // // 客户端配置,主要是全局的配置信息 29 | // ClientConfiguration clientConfiguration = new ClientConfiguration(); 30 | // clientConfiguration.setMaxConnections(100); 31 | // // url以及region配置 32 | // AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(ossProperties.getEndpoint(), ossProperties.getRegion()); 33 | // // 凭证配置 34 | // AWSCredentials awsCredentials = new BasicAWSCredentials(ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret()); 35 | // AWSCredentialsProvider awsCredentialsProvider = new AWSStaticCredentialsProvider(awsCredentials); 36 | // // build amazonS3Client客户端 37 | // return AmazonS3Client.builder() 38 | // .withEndpointConfiguration(endpointConfiguration) 39 | // .withClientConfiguration(clientConfiguration) 40 | // .withCredentials(awsCredentialsProvider) 41 | // .disableChunkedEncoding() 42 | // .withPathStyleAccessEnabled(true) 43 | // .build(); 44 | // } 45 | // 46 | // @Bean 47 | // @ConditionalOnBean(AmazonS3.class) 48 | // public OssTemplate ossTemplate(AmazonS3 amazonS3) { 49 | // return new OssTemplate(amazonS3); 50 | // } 51 | //} 52 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/config/OssConfigProperties.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | import lombok.Data; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.validation.annotation.Validated; 8 | 9 | /** 10 | * oss属性配置 11 | * 12 | * @author ngcly 13 | * @version V1.0 14 | * @since 2022/7/30 20:48 15 | */ 16 | @Data 17 | @Validated 18 | @Configuration 19 | @ConfigurationProperties(prefix = "oss.config") 20 | public class OssConfigProperties { 21 | /** 22 | * oss域名 23 | */ 24 | @NotBlank 25 | private String host; 26 | /** 27 | * 端点 28 | */ 29 | private String endpoint; 30 | /** 31 | * 访问秘钥id 32 | */ 33 | @NotBlank 34 | private String accessKeyId; 35 | /** 36 | * 访问秘钥密码 37 | */ 38 | @NotBlank 39 | private String accessKeySecret; 40 | /** 41 | * 存储桶名称 42 | */ 43 | private String bucketName; 44 | } 45 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/config/RabbitConfig.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.amqp.core.*; 6 | import org.springframework.amqp.rabbit.annotation.EnableRabbit; 7 | import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; 8 | import org.springframework.amqp.rabbit.core.RabbitTemplate; 9 | import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; 10 | import org.springframework.amqp.support.converter.MessageConverter; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | /** 18 | * RabbitMQ 配置 19 | * 20 | * @author ngcly 21 | */ 22 | @Configuration 23 | @EnableRabbit 24 | public class RabbitConfig { 25 | private static final Logger log = LoggerFactory.getLogger(RabbitConfig.class); 26 | 27 | /** 28 | * 通知队列 29 | */ 30 | public static final String NOTIFY_QUEUE = "notify-queue"; 31 | 32 | /** 33 | * 延时队列 34 | **/ 35 | public static final String DELAY_QUEUE = "delay-queue"; 36 | /** 37 | * 延时交换机 38 | **/ 39 | public static final String DELAY_EXCHANGE = "delay-exchange"; 40 | /** 41 | * 延时路由名称 42 | */ 43 | public static final String DELAY_ROUTING_KEY = "delay-key"; 44 | 45 | /** 46 | * 配置摸板(非必要 测试延迟队列方便直观) 47 | * 48 | * @param connectionFactory 连接工厂 49 | * @return RabbitTemplate 50 | */ 51 | @Bean 52 | public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory) { 53 | RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); 54 | rabbitTemplate.setMandatory(true); 55 | rabbitTemplate.setMessageConverter(messageConverter()); 56 | rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> 57 | log.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, ack, cause)); 58 | rabbitTemplate.setReturnsCallback(returned -> 59 | log.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}", 60 | returned.getExchange(), returned.getRoutingKey(), returned.getReplyCode(), 61 | returned.getReplyText(), returned.getMessage())); 62 | return rabbitTemplate; 63 | } 64 | 65 | /** 66 | * rabbitmq 默认将信息对象按照jdk序列化 此处改为json序列化 67 | */ 68 | @Bean 69 | public MessageConverter messageConverter() { 70 | return new Jackson2JsonMessageConverter(); 71 | } 72 | 73 | /** 74 | * 默认队列 该队列会自动被 rabbitmq 绑定到默认的交换机上 75 | */ 76 | @Bean 77 | public Queue notifyQueue() { 78 | // 第一个是 QUEUE 的名字,第二个是消息是否需要持久化处理 79 | return new Queue(NOTIFY_QUEUE, true); 80 | } 81 | 82 | /** 83 | * 延迟队列配置 以下采用了延迟插件 84 | **/ 85 | @Bean 86 | public Queue delayQueue() { 87 | return new Queue(DELAY_QUEUE, true); 88 | } 89 | 90 | /** 91 | * 延迟交换机配置 92 | **/ 93 | @Bean 94 | public CustomExchange delayExchange() { 95 | Map map = new HashMap<>(16); 96 | map.put("x-delayed-type", "direct"); 97 | return new CustomExchange(DELAY_EXCHANGE, "x-delayed-message", true, false, map); 98 | } 99 | 100 | /** 101 | * 延迟交换机与延迟队列绑定 102 | */ 103 | @Bean 104 | Binding bindingDelayExchangeQueue() { 105 | return BindingBuilder.bind(delayQueue()).to(delayExchange()).with(DELAY_ROUTING_KEY).noargs(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/config/RestClientConfig.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.http.client.SimpleClientHttpRequestFactory; 6 | import org.springframework.web.client.RestClient; 7 | 8 | /** 9 | * @author chenning 10 | */ 11 | @Configuration 12 | public class RestClientConfig { 13 | @Bean 14 | public RestClient restClient(){ 15 | SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); 16 | requestFactory.setConnectTimeout(30000); 17 | requestFactory.setReadTimeout(30000); 18 | return RestClient.builder() 19 | .requestFactory(requestFactory) 20 | .build(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/config/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.messaging.simp.config.MessageBrokerRegistry; 5 | import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; 6 | import org.springframework.web.socket.config.annotation.StompEndpointRegistry; 7 | import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; 8 | 9 | /** 10 | * webSocket 配置 11 | * @author ngcly 12 | */ 13 | @Configuration 14 | @EnableWebSocketMessageBroker 15 | public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { 16 | 17 | /** 18 | * 配置消息代理(中介) 19 | * enableSimpleBroker 服务端推送给客户端的路径前缀 20 | * setApplicationDestinationPrefixes 客户端发送数据给服务器端的一个前缀 21 | */ 22 | @Override 23 | public void configureMessageBroker(MessageBrokerRegistry config) { 24 | config.enableSimpleBroker("/topic","/user"); 25 | config.setApplicationDestinationPrefixes("/app"); 26 | } 27 | 28 | /** 29 | * 注册端点,发布或者订阅消息的时候需要连接此端点 30 | * setAllowedOrigins 非必须,*表示允许其他域进行连接 31 | * withSockJS 表示开始socket js支持 32 | */ 33 | @Override 34 | public void registerStompEndpoints(StompEndpointRegistry registry) { 35 | registry.addEndpoint("/chat").setAllowedOriginPatterns("*").withSockJS(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/enums/ConfigEnum.java: -------------------------------------------------------------------------------- 1 | package com.cn.enums; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * 配置相关 枚举 7 | * @author ngcly 8 | */ 9 | @Getter 10 | public enum ConfigEnum { 11 | /** 12 | * 密钥属性 13 | */ 14 | JASYPT_ENCRYPTOR("jasypt.encryptor.password","ngCly"); 15 | 16 | private final String key; 17 | private final String value; 18 | 19 | ConfigEnum(String key, String value) { 20 | this.key = key; 21 | this.value = value; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/enums/EssayStatusEnum.java: -------------------------------------------------------------------------------- 1 | package com.cn.enums; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * @author ngcly 7 | */ 8 | @Getter 9 | public enum EssayStatusEnum { 10 | DRAFT("草稿"), PENDING("待审核"), FORBIDDEN("查封"), NORMAL("已确认"), RECOMMEND("推荐"); 11 | 12 | private final String msg; 13 | 14 | EssayStatusEnum(String msg) { 15 | this.msg = msg; 16 | } 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/enums/FaveTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.cn.enums; 2 | 3 | /** 4 | * @author chenning 5 | */ 6 | public enum FaveTypeEnum { 7 | LIKE, COLLECT; 8 | } 9 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/enums/GenderEnum.java: -------------------------------------------------------------------------------- 1 | package com.cn.enums; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * 性别 枚举 7 | * @author ngcly 8 | * @version V1.0 9 | * @since 2021/8/24 0:23 10 | */ 11 | @Getter 12 | public enum GenderEnum { 13 | MAN("男"),WOMAN("女"); 14 | 15 | private final String gender; 16 | 17 | GenderEnum(String gender) { 18 | this.gender = gender; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/enums/LoginStatusEnum.java: -------------------------------------------------------------------------------- 1 | package com.cn.enums; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | /** 6 | * @author chenning 7 | */ 8 | public enum LoginStatusEnum { 9 | @JsonProperty("success") 10 | SUCCESS, 11 | @JsonProperty("failure") 12 | FAILURE 13 | } 14 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/enums/ResourceEnum.java: -------------------------------------------------------------------------------- 1 | package com.cn.enums; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | /** 6 | * @author chenning 7 | */ 8 | public enum ResourceEnum { 9 | @JsonProperty("menu") 10 | MENU, 11 | @JsonProperty("button") 12 | BUTTON 13 | } 14 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/enums/SocialEnum.java: -------------------------------------------------------------------------------- 1 | package com.cn.enums; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * 三方枚举 7 | * @author ngcly 8 | * @version V1.0 9 | * @since 2021/8/26 17:39 10 | */ 11 | @Getter 12 | public enum SocialEnum { 13 | /** 腾讯QQ */ 14 | QQ("qq","qq","101447968","46474c655bd4f21ddc55cf827e2f04be"), 15 | /** 腾讯微信 */ 16 | WECHAT("wechat","微信","xxxxxxxxxx","xxxxxxxxxxxxxxxxx"); 17 | 18 | private final String source; 19 | private final String name; 20 | private final String appId; 21 | private final String appSecret; 22 | 23 | SocialEnum(String source, String name, String appId, String appSecret) { 24 | this.source = source; 25 | this.name = name; 26 | this.appId = appId; 27 | this.appSecret = appSecret; 28 | } 29 | 30 | /**回调地址填写前端地址 不然后面拿到用户信息后没办法通知前端登录成功*/ 31 | public static final String APP_REDIRECT = "https://localhost/oauth/callback"; 32 | 33 | /**为了简单起见,这里直接用固定值,该值是为了防止 csrf 攻击*/ 34 | public static final String STATE = "cly"; 35 | } 36 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/enums/SocialParamEnum.java: -------------------------------------------------------------------------------- 1 | package com.cn.enums; 2 | 3 | /** 4 | * 三方参数 5 | * @author ngcly 6 | * @version V1.0 7 | * @since 2021/8/26 20:30 8 | */ 9 | public enum SocialParamEnum { 10 | /**三方接口返回相关参数*/ 11 | code, msg, access_token, expires_in, refresh_token, openid, errcode, errmsg, scope, ret, nickname, figureurl_qq_1, 12 | gender, unionid, sex, headimgurl, country, province, city 13 | } 14 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/enums/UserStatusEnum.java: -------------------------------------------------------------------------------- 1 | package com.cn.enums; 2 | 3 | /** 4 | * @author chenning 5 | */ 6 | public enum UserStatusEnum { 7 | INITIALIZE, NORMAL, LOCKED; 8 | } 9 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/enums/UserTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.cn.enums; 2 | 3 | public enum UserTypeEnum { 4 | ADMIN, USER; 5 | } 6 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/exception/FileUploadException.java: -------------------------------------------------------------------------------- 1 | package com.cn.exception; 2 | 3 | import com.cn.model.RestCode; 4 | 5 | /** 6 | * @author chenning 7 | */ 8 | public class FileUploadException extends GlobalException{ 9 | public FileUploadException() { 10 | super(RestCode.FILE_UPLOAD_ERR); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/exception/GlobalException.java: -------------------------------------------------------------------------------- 1 | package com.cn.exception; 2 | 3 | import com.cn.model.RestCode; 4 | 5 | /** 6 | * @author ngcly 7 | * 自定义全局异常 8 | * @since 2019/5/18 11:58 9 | */ 10 | public class GlobalException extends RuntimeException { 11 | private final int code; 12 | private final String message; 13 | 14 | public GlobalException(RestCode restCode){ 15 | this(restCode.code,restCode.msg); 16 | } 17 | 18 | public GlobalException(String msg){ 19 | this(RestCode.SERVER_ERROR.code,msg); 20 | } 21 | 22 | public GlobalException(int code, String msg) { 23 | super(msg); 24 | this.code = code; 25 | this.message = msg; 26 | } 27 | 28 | public int getCode() { 29 | return code; 30 | } 31 | 32 | 33 | @Override 34 | public String getMessage() { 35 | return message; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/model/AuthenticationDetails.java: -------------------------------------------------------------------------------- 1 | package com.cn.model; 2 | 3 | import com.cn.util.IpUtil; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import jakarta.servlet.http.HttpSession; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.Getter; 8 | import lombok.ToString; 9 | import org.apache.http.HttpHeaders; 10 | import org.springframework.security.core.SpringSecurityCoreVersion; 11 | 12 | import java.io.Serial; 13 | import java.io.Serializable; 14 | import java.util.Objects; 15 | 16 | /** 17 | * @author chenning 18 | */ 19 | @Getter 20 | @ToString 21 | @EqualsAndHashCode 22 | public class AuthenticationDetails implements Serializable { 23 | @Serial 24 | private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; 25 | private static final String CAPTCHA_KEY = "captcha"; 26 | 27 | private final CaptchaInfo captchaInfo; 28 | 29 | private final String remoteAddress; 30 | 31 | private final String userAgent; 32 | 33 | private final String userCaptcha; 34 | 35 | public AuthenticationDetails(HttpServletRequest request) { 36 | this(extractCaptchaInfo(request), IpUtil.getIpAddress(request), 37 | request.getHeader(HttpHeaders.USER_AGENT), request.getParameter(CAPTCHA_KEY)); 38 | } 39 | 40 | public AuthenticationDetails(CaptchaInfo captchaInfo, String remoteAddress, 41 | String userAgent, String userCaptcha) { 42 | this.captchaInfo = captchaInfo; 43 | this.remoteAddress = remoteAddress; 44 | this.userAgent = userAgent; 45 | this.userCaptcha = userCaptcha; 46 | } 47 | 48 | private static CaptchaInfo extractCaptchaInfo(HttpServletRequest request) { 49 | HttpSession session = request.getSession(false); 50 | CaptchaInfo verificationCode = null; 51 | if (session != null) { 52 | verificationCode = (CaptchaInfo) session.getAttribute(CAPTCHA_KEY); 53 | session.removeAttribute(CAPTCHA_KEY); 54 | } 55 | return verificationCode; 56 | } 57 | 58 | public boolean isAdministrator() { 59 | return Objects.nonNull(captchaInfo); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/model/CaptchaInfo.java: -------------------------------------------------------------------------------- 1 | package com.cn.model; 2 | 3 | import java.io.Serializable; 4 | import java.time.LocalDateTime; 5 | 6 | /** 7 | * 验证码 数据 8 | * 9 | * @author ngcly 10 | */ 11 | public record CaptchaInfo(String code, LocalDateTime expireTime) implements Serializable { 12 | 13 | public CaptchaInfo(String code, int expireIn) { 14 | this(code, LocalDateTime.now().plusSeconds(expireIn)); 15 | } 16 | 17 | public boolean isExpire() { 18 | return LocalDateTime.now().isAfter(expireTime); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/model/MenuDTO.java: -------------------------------------------------------------------------------- 1 | package com.cn.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import java.io.Serializable; 7 | import java.util.List; 8 | import java.util.Objects; 9 | 10 | /** 11 | * 树数据封装类 12 | * 13 | * @author ngcly 14 | */ 15 | @Getter 16 | @Setter 17 | public class MenuDTO implements Serializable { 18 | public static final Long rootId = 0L; 19 | 20 | protected Long id; 21 | 22 | /** 名称 */ 23 | protected String name; 24 | 25 | /** 父id */ 26 | protected Long parentId; 27 | 28 | /** 链接地址 */ 29 | protected String url; 30 | 31 | /** 是否勾选 */ 32 | protected Boolean checked; 33 | 34 | /** 图标 */ 35 | protected String icon; 36 | 37 | /** 子菜单 */ 38 | protected List children; 39 | 40 | @Override 41 | public boolean equals(Object o) { 42 | if (this == o) { 43 | return true; 44 | } 45 | if (o == null || getClass() != o.getClass()) { 46 | return false; 47 | } 48 | MenuDTO menuDTO = (MenuDTO) o; 49 | return id.equals(menuDTO.id) && name.equals(menuDTO.name) && parentId.equals(menuDTO.parentId) && url.equals(menuDTO.url); 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | return Objects.hash(id, name, parentId, url); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/model/RestCode.java: -------------------------------------------------------------------------------- 1 | package com.cn.model; 2 | 3 | /** 4 | * restful 风格 错误编码 5 | * @author ngcly 6 | */ 7 | public enum RestCode { 8 | SUCCESS(0, "操作成功"), 9 | VERIFY_CODE_ERR(408,"验证码错误或过期"), 10 | USER_ERR(407,"用户名或密码错误"), 11 | TOKEN_EXPIRE(409,"Token过期"), 12 | USER_DISABLE(334,"用户被禁用"), 13 | USER_LOCKED(335,"用户被锁定"), 14 | USER_EXPIRE(336,"账号过期"), 15 | PASSWORD_EXPIRE(337,"密码过期"), 16 | UNAUTHORIZED(301 ,"权限不足"), 17 | UNION_DUMP(333, "唯一标识符重复"), 18 | PARAM_ERROR(400, "参数不合法"), 19 | NOT_LOGIN(401, "未登录"), 20 | NOT_FOUND(404 ,"路径错误"), 21 | HEAD_ERROR(415, "头部不匹配"), 22 | METHOD_ERROR(405 ,"不支持该请求方式"), 23 | SERVER_ERROR(500, "服务异常"), 24 | FILE_UPLOAD_ERR(611,"文件上传失败"); 25 | 26 | public final int code; 27 | public final String msg; 28 | 29 | RestCode(int code, String msg) { 30 | this.code = code; 31 | this.msg = msg; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/util/MenuUtil.java: -------------------------------------------------------------------------------- 1 | package com.cn.util; 2 | 3 | import cn.hutool.core.collection.CollUtil; 4 | import com.cn.model.MenuDTO; 5 | 6 | import java.util.*; 7 | 8 | /** 9 | * 菜单工具类 10 | * 11 | * @author ngcly 12 | * @date 2018-01-02 17:54 13 | */ 14 | public final class MenuUtil { 15 | private MenuUtil() { 16 | } 17 | 18 | /** 19 | * 判断菜单是否被勾选 20 | * 21 | * @param originMenus 原菜单 22 | * @param roleMenus 角色对应菜单 23 | */ 24 | public static Collection checkMenuSelected(Collection originMenus, Collection roleMenus) { 25 | List menuList = new ArrayList<>(); 26 | originMenus.forEach(t -> { 27 | t.setChecked(roleMenus.contains(t)); 28 | menuList.add(t); 29 | }); 30 | return makeMenuToTree(menuList); 31 | } 32 | 33 | /** 34 | * 将菜单list转换成树状 35 | */ 36 | public static Collection makeMenuToTree(Collection menuList) { 37 | Set rootTrees = new HashSet<>(); 38 | menuList.forEach(menu -> { 39 | if(MenuDTO.rootId.equals(menu.getParentId())){ 40 | rootTrees.add(menu); 41 | } 42 | menuList.forEach(menuDTO -> { 43 | if(menu.getId().equals(menuDTO.getParentId())){ 44 | if(menu.getChildren()==null){ 45 | menu.setChildren(CollUtil.newArrayList(menuDTO)); 46 | }else{ 47 | menu.getChildren().add(menuDTO); 48 | } 49 | } 50 | }); 51 | }); 52 | return menuTreeSort(rootTrees); 53 | } 54 | 55 | /** 56 | * 将菜单进行树状排序 57 | */ 58 | public static List menuTreeSort(Collection list) { 59 | List resultList = new ArrayList<>(); 60 | sortList(list, MenuDTO.rootId, resultList); 61 | return resultList; 62 | } 63 | 64 | public static void sortList(Collection list, Long id, Collection resultList) { 65 | list.forEach(menu -> { 66 | if(id.equals(menu.getParentId())) { 67 | resultList.add(menu); 68 | sortList(list, menu.getId(),resultList); 69 | } 70 | }); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/util/Result.java: -------------------------------------------------------------------------------- 1 | package com.cn.util; 2 | 3 | import com.cn.model.RestCode; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import lombok.Getter; 6 | import org.springframework.lang.Nullable; 7 | 8 | /** 9 | * 返回体 10 | * 11 | * @author ngcly 12 | * @version V1.0 13 | * @since 2020/5/18 11:04 14 | */ 15 | @Getter 16 | @Schema(description = "返回体") 17 | public class Result { 18 | 19 | @Schema(title = "状态码") 20 | private final int code; 21 | @Schema(title = "说明信息") 22 | private final String msg; 23 | @Schema(title = "内容") 24 | private final T data; 25 | 26 | private Result(int code, String msg) { 27 | this(code, msg, null); 28 | } 29 | 30 | private Result(int code, String msg, @Nullable T data) { 31 | this.code = code; 32 | this.msg = msg; 33 | this.data = data; 34 | } 35 | 36 | /** 37 | * 默认成功返回 38 | */ 39 | public static Result success() { 40 | return new Result<>(RestCode.SUCCESS.code, RestCode.SUCCESS.msg); 41 | } 42 | 43 | /** 44 | * 一般成功 统一返回 45 | */ 46 | public static Result success(T data) { 47 | return new Result<>(RestCode.SUCCESS.code, RestCode.SUCCESS.msg, data); 48 | } 49 | 50 | /** 51 | * 自定义成功消息 52 | */ 53 | public static Result success(String msg, T data) { 54 | return new Result<>(RestCode.SUCCESS.code, msg, data); 55 | } 56 | 57 | /** 58 | * 自定义成功返回 59 | */ 60 | public static Result success(int code, String msg, T data) { 61 | return new Result<>(code, msg, data); 62 | } 63 | 64 | /** 65 | * layui 表格返回 66 | */ 67 | public static LayuiResult success(Long count, T data){ 68 | return LayuiResult.ok(count,data); 69 | } 70 | 71 | /** 72 | * 一般错误 统一返回 73 | */ 74 | public static Result failure(RestCode restCode) { 75 | return new Result<>(restCode.code, restCode.msg); 76 | } 77 | 78 | /** 79 | * 自定义错误信息 80 | */ 81 | public static Result failure(int code, String msg) { 82 | return new Result<>(code, msg); 83 | } 84 | 85 | 86 | static class LayuiResult extends Result { 87 | @Getter 88 | private final Long count; 89 | 90 | private LayuiResult(int code, String msg,T data,Long count) { 91 | super(code,msg,data); 92 | this.count = count; 93 | } 94 | 95 | public static LayuiResult ok(Long count, T data){ 96 | return new LayuiResult<>(RestCode.SUCCESS.code, RestCode.SUCCESS.msg, data, count); 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /commons/src/main/java/com/cn/util/UploadUtil.java: -------------------------------------------------------------------------------- 1 | package com.cn.util; 2 | 3 | import cn.hutool.extra.spring.SpringUtil; 4 | import com.aliyun.oss.OSS; 5 | import com.aliyun.oss.OSSClientBuilder; 6 | import com.cn.config.OssConfigProperties; 7 | import com.cn.exception.FileUploadException; 8 | import org.springframework.web.multipart.MultipartFile; 9 | 10 | import jakarta.annotation.Nonnull; 11 | import java.io.ByteArrayInputStream; 12 | import java.io.IOException; 13 | import java.net.URL; 14 | import java.security.NoSuchAlgorithmException; 15 | import java.security.SecureRandom; 16 | import java.util.Date; 17 | import java.util.Objects; 18 | import java.util.Random; 19 | 20 | /** 21 | * 文件上传工具类 22 | * 23 | * @author ngcly 24 | */ 25 | public final class UploadUtil { 26 | private static final OssConfigProperties ossProperties; 27 | private static final Random rand; 28 | 29 | private UploadUtil() { 30 | } 31 | 32 | static { 33 | ossProperties = SpringUtil.getBean("ossConfigProperties"); 34 | try { 35 | rand = SecureRandom.getInstanceStrong(); 36 | } catch (NoSuchAlgorithmException e) { 37 | throw new RuntimeException(e); 38 | } 39 | } 40 | 41 | /** 42 | * 阿里云上传 43 | * 44 | * @param file 文件 45 | * @param cloudDir 上传至服务器目录 46 | * @return String 访问路径 47 | */ 48 | public static String uploadFileByAli(@Nonnull MultipartFile file, String cloudDir) { 49 | try { 50 | OSS ossClient = new OSSClientBuilder().build(ossProperties.getHost(), ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret()); 51 | // 上传内容到指定的存储空间(bucketName)并保存为指定的文件名称(objectName)。 52 | String suffix = Objects.requireNonNull(file.getOriginalFilename()) 53 | .substring(file.getOriginalFilename().lastIndexOf(".")).toLowerCase(); 54 | String fileName = rand.nextInt(10000) + System.currentTimeMillis() + suffix; 55 | String fileKey = cloudDir + "/" + fileName; 56 | ossClient.putObject(ossProperties.getBucketName(), fileKey, new ByteArrayInputStream(file.getBytes())); 57 | // 设置URL过期时间为10年 3600l* 1000*24*365*10 58 | Date expiration = new Date(System.currentTimeMillis() + 3600L * 1000 * 24 * 365 * 10); 59 | // 生成URL 60 | URL url = ossClient.generatePresignedUrl(ossProperties.getBucketName(), fileKey, expiration); 61 | // 关闭OSSClient 62 | ossClient.shutdown(); 63 | return url.toString().replace("music-story.oss-cn-hongkong-internal.aliyuncs.com", ossProperties.getEndpoint()); 64 | } catch (IOException e) { 65 | throw new FileUploadException(); 66 | } 67 | } 68 | 69 | /** 70 | * 阿里云删除文件 71 | * 72 | * @param url 图片地址 73 | */ 74 | public static void deleteFileByAli(String url) { 75 | // 创建OSSClient实例。 76 | OSS ossClient = new OSSClientBuilder().build(ossProperties.getHost(), ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret()); 77 | // 删除文件。 78 | String fileName = url.replace(ossProperties.getEndpoint(), ""); 79 | ossClient.deleteObject(ossProperties.getBucketName(), fileName.substring(fileName.lastIndexOf(":/"), fileName.indexOf("?"))); 80 | // 关闭OSSClient。 81 | ossClient.shutdown(); 82 | } 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /commons/src/test/java/IpUtilTest.java: -------------------------------------------------------------------------------- 1 | import com.cn.util.IpUtil; 2 | import org.junit.jupiter.api.Assertions; 3 | import org.junit.jupiter.api.Test; 4 | 5 | /** 6 | * @author chenning 7 | */ 8 | class IpUtilTest { 9 | 10 | @Test 11 | void test_getIpAddress() { 12 | String ipAddr = IpUtil.getIpAddresses("111.175.37.78"); 13 | Assertions.assertNotNull(ipAddr); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /commons/src/test/java/JasyptTest.java: -------------------------------------------------------------------------------- 1 | import com.ulisesbocchio.jasyptspringboot.encryptor.DefaultLazyEncryptor; 2 | import org.jasypt.encryption.StringEncryptor; 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.core.env.StandardEnvironment; 6 | 7 | /** 8 | * @author ngcly 9 | */ 10 | class JasyptTest { 11 | @Test 12 | void encode(){ 13 | System.setProperty("jasypt.encryptor.password","test_salt"); 14 | StringEncryptor encryptor = new DefaultLazyEncryptor(new StandardEnvironment()); 15 | String password = "password_test"; 16 | String encryptStr = encryptor.encrypt(password); 17 | System.out.println(encryptStr); 18 | Assertions.assertEquals(password,encryptor.decrypt(encryptStr)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | elasticsearch: 4 | image: docker.elastic.co/elasticsearch/elasticsearch:8.9.1 5 | container_name: elasticsearch 6 | privileged: true #赋予这个容器的root权限 7 | ports: 8 | - "9200:9200" 9 | - "9300:9300" 10 | volumes: 11 | - ./es/data:/usr/share/elasticsearch/data #数据文件挂载 12 | - ./es/logs:/usr/share/elasticsearch/logs #日志挂载 13 | - ./es/plugins:/usr/share/elasticsearch/plugins #插件文件挂载 14 | environment: 15 | - "node.name=elasticsearch" 16 | - "cluster.name=elasticsearch" #设置集群名称为elasticsearch 17 | - "discovery.type=single-node" #以单一节点模式启动 18 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m" #设置使用jvm内存大小 19 | - "xpack.security.enabled=false" #关闭xpack认证 20 | labels: 21 | org.springframework.boot.service-connection: elasticsearch 22 | 23 | kibana: 24 | image: docker.elastic.co/kibana/kibana:8.9.1 25 | container_name: kibana 26 | privileged: true 27 | links: 28 | - elasticsearch:es #可以用es这个域名访问elasticsearch服务 29 | depends_on: 30 | - elasticsearch #kibana在elasticsearch启动之后再启动 31 | environment: 32 | - "elasticsearch.hosts=http://es:9200" #设置访问elasticsearch的地址 33 | # - elasticsearch.username="kibana_system" 34 | # - elasticsearch.password="123" 35 | - "xpack.management.enabled=false" 36 | - "i18n.local=zh-CN" 37 | ports: 38 | - "5601:5601" 39 | 40 | logstash: 41 | image: docker.elastic.co/logstash/logstash:8.9.1 42 | container_name: logstash 43 | privileged: true 44 | volumes: 45 | - ./logstash/logstash.conf:/usr/share/logstash/pipeline/logstash.conf #挂载logstash的配置文件 46 | depends_on: 47 | - elasticsearch 48 | links: 49 | - elasticsearch:es 50 | environment: 51 | - "xpack.management.enabled=false" 52 | - "xpack.monitoring.elasticsearch.hosts= http://es:9200" 53 | ports: 54 | - "4560:4560" 55 | 56 | # fluentd: 57 | # image: fluent/fluentd:latest 58 | # container_name: fluentd 59 | # volumes: 60 | # - /Users/chenning/dockerCompose/mydata/fluentd/log:/fluentd/log 61 | # - /Users/chenning/dockerCompose/mydata/fluentd/fluent.conf:/fluentd/etc/fluent.conf 62 | # depends_on: 63 | # - elasticsearch 64 | # links: 65 | # - elasticsearch:es 66 | # ports: 67 | # - "24224:24224" 68 | 69 | # filebeat: 70 | # image: docker.elastic.co/beats/filebeat:8.9.1 71 | # container_name: filebeat 72 | # links: 73 | # - elasticsearch:es 74 | # depends_on: 75 | # - elasticsearch 76 | # - kibana 77 | # environment: 78 | # - "output.elasticsearch.hosts=http://es:9200" 79 | 80 | redis: 81 | image: redis:latest 82 | container_name: redis 83 | ports: 84 | - "6379:6379" 85 | labels: 86 | org.springframework.boot.service-connection: redis 87 | 88 | rabbitmq: 89 | image: rabbitmq:3.12-management 90 | container_name: rabbitmq 91 | privileged: true 92 | ports: 93 | - "15672:15672" 94 | - "5672:5672" 95 | labels: 96 | org.springframework.boot.service-connection: rabbitmq -------------------------------------------------------------------------------- /fluent.conf: -------------------------------------------------------------------------------- 1 | 2 | @type tcp 3 | @id record-input 4 | port 24224 5 | tag record 6 | 7 | @type json 8 | 9 | 10 | 11 | 12 | @type stdout 13 | output_type json 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngcly/MusicStory/183675ed994f9037033e8406bb3999c4b43aa2a9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /interface/Dockerfile: -------------------------------------------------------------------------------- 1 | # FROM 表示使用 Jdk17 环境 为基础镜像,如果镜像不是本地的会从 DockerHub 进行下载 2 | FROM eclipse-temurin:17.0.3_7-jre 3 | # VOLUME 指向了一个/tmp的目录,由于 Spring Boot 使用内置的Tomcat容器,Tomcat 默认使用/tmp作为工作目录。这个命令的效果是:在宿主机的/var/lib/docker目录下创建一个临时文件并把它链接到容器中的/tmp目录 4 | VOLUME /tmp 5 | 6 | ARG PROJECT_NAME=interface 7 | ARG JAR_FILE=build/libs/${PROJECT_NAME}-*.jar 8 | ENV APP_JAR=mustic-story-${PROJECT_NAME}.jar 9 | COPY ${JAR_FILE} ${APP_JAR} 10 | ENTRYPOINT java -jar ${APP_JAR} -------------------------------------------------------------------------------- /interface/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { 2 | archiveBaseName = "interface" 3 | archiveVersion = '1.0' 4 | } 5 | 6 | dependencies { 7 | implementation project(":service") 8 | 9 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 10 | implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:${docVersion}" 11 | } 12 | -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/InterfaceApplication.java: -------------------------------------------------------------------------------- 1 | package com.cn; 2 | 3 | import com.cn.enums.ConfigEnum; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | /** 8 | * @author ngcly 9 | */ 10 | @SpringBootApplication 11 | public class InterfaceApplication { 12 | public static void main(String[] args) { 13 | ConfigEnum configEnum = ConfigEnum.JASYPT_ENCRYPTOR; 14 | System.setProperty(configEnum.getKey(),configEnum.getValue()); 15 | SpringApplication.run(InterfaceApplication.class, args); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/amqp/MqReceive.java: -------------------------------------------------------------------------------- 1 | package com.cn.amqp; 2 | 3 | import com.cn.UserRelatedService; 4 | import com.cn.UserService; 5 | import com.cn.config.RabbitConfig; 6 | import com.cn.entity.News; 7 | import lombok.AllArgsConstructor; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 11 | import org.springframework.stereotype.Component; 12 | 13 | /** 14 | * RabbitMQ 队列消费 15 | * @author ngcly 16 | */ 17 | @Component 18 | @AllArgsConstructor 19 | public class MqReceive { 20 | private static final Logger log = LoggerFactory.getLogger(MqReceive.class); 21 | 22 | private final UserService userService; 23 | private final UserRelatedService userRelatedService; 24 | 25 | @RabbitListener(queues = {RabbitConfig.DELAY_QUEUE}) 26 | public void consumeDelay(Long userId) { 27 | log.info("[执行清除过期未激活账号] - [{}]", userId); 28 | userService.delUnActiveUser(userId); 29 | } 30 | 31 | @RabbitListener(queues = {RabbitConfig.NOTIFY_QUEUE}) 32 | public void consumeNotify(News news) { 33 | log.info("[消息通知] - [{}]", news); 34 | userRelatedService.notifyUser(news); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/config/CorsConfig.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.core.Ordered; 5 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 6 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 | 9 | /** 10 | * 跨域配置 11 | * @author ngcly 12 | * @version V1.0 13 | * @since 2021/8/24 22:43 14 | */ 15 | @Configuration 16 | public class CorsConfig implements WebMvcConfigurer { 17 | 18 | /**设置swagger 为默认主页*/ 19 | @Override 20 | public void addViewControllers(ViewControllerRegistry registry) { 21 | registry.addViewController("/").setViewName("redirect:/swagger-ui.html"); 22 | registry.setOrder(Ordered.HIGHEST_PRECEDENCE); 23 | WebMvcConfigurer.super.addViewControllers(registry); 24 | } 25 | 26 | @Override 27 | public void addCorsMappings(CorsRegistry registry) { 28 | //添加映射路径 29 | registry.addMapping("/**") 30 | //设置放行哪些原始域 31 | .allowedOriginPatterns("*") 32 | //是否发送Cookie 33 | .allowCredentials(true) 34 | //放行哪些请求方式 35 | .allowedMethods("*") 36 | //允许头部 37 | .allowedHeaders("*") 38 | // 跨域允许时间 39 | .maxAge(3600); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/config/ExceptionHandle.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import com.cn.exception.GlobalException; 4 | import com.cn.model.RestCode; 5 | import com.cn.util.Result; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.http.ProblemDetail; 8 | import org.springframework.security.access.AccessDeniedException; 9 | import org.springframework.security.authentication.BadCredentialsException; 10 | import org.springframework.web.HttpMediaTypeNotAcceptableException; 11 | import org.springframework.web.HttpRequestMethodNotSupportedException; 12 | import org.springframework.web.bind.MethodArgumentNotValidException; 13 | import org.springframework.web.bind.MissingPathVariableException; 14 | import org.springframework.web.bind.MissingServletRequestParameterException; 15 | import org.springframework.web.bind.annotation.ControllerAdvice; 16 | import org.springframework.web.bind.annotation.ExceptionHandler; 17 | import org.springframework.web.bind.annotation.ResponseBody; 18 | import org.springframework.web.bind.annotation.RestControllerAdvice; 19 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 20 | 21 | import jakarta.servlet.http.HttpServletRequest; 22 | import jakarta.validation.ConstraintViolationException; 23 | 24 | /** 25 | * 全局统一异常处理 26 | * @author ngcly 27 | */ 28 | @Slf4j 29 | @RestControllerAdvice 30 | public class ExceptionHandle extends ResponseEntityExceptionHandler { 31 | 32 | // @ExceptionHandler(value 33 | // = { IllegalArgumentException.class, IllegalStateException.class }) 34 | // protected ResponseEntity handleConflict( 35 | // RuntimeException ex, WebRequest request) { 36 | // String bodyOfResponse = "This should be application specific"; 37 | // return handleExceptionInternal(ex, bodyOfResponse, 38 | // new HttpHeaders(), HttpStatus.CONFLICT, request); 39 | // } 40 | 41 | /** 42 | * 全局异常处理 43 | */ 44 | @ResponseBody 45 | @ExceptionHandler(value = Exception.class) 46 | public Result handlerException(HttpServletRequest request, Exception e){ 47 | log.error(request.getRequestURI(), e); 48 | if(e instanceof BadCredentialsException) { 49 | return Result.failure(RestCode.USER_ERR); 50 | } else if (e instanceof AccessDeniedException) { 51 | return Result.failure(RestCode.UNAUTHORIZED); 52 | } else if (e instanceof HttpRequestMethodNotSupportedException) { 53 | return Result.failure(RestCode.METHOD_ERROR); 54 | } else if (e instanceof MissingPathVariableException) { 55 | // 缺少路径参数 56 | return Result.failure(RestCode.NOT_FOUND); 57 | } else if (e instanceof MissingServletRequestParameterException) { 58 | // 缺少必须的请求参数 59 | return Result.failure(RestCode.PARAM_ERROR); 60 | } else if (e instanceof HttpMediaTypeNotAcceptableException){ 61 | return Result.failure(RestCode.HEAD_ERROR); 62 | }else if (e instanceof GlobalException exception){ 63 | return Result.failure(exception.getCode(),exception.getMessage()); 64 | } else if (e instanceof ConstraintViolationException exception) { 65 | //@RequestParam 参数校验失败 66 | return Result.failure(RestCode.PARAM_ERROR.code, exception.getMessage()); 67 | } else if (e instanceof MethodArgumentNotValidException exception){ 68 | return Result.failure(RestCode.PARAM_ERROR.code, exception.getMessage()); 69 | } else { 70 | return Result.failure(RestCode.SERVER_ERROR); 71 | } 72 | } 73 | 74 | // @ExceptionHandler 75 | // ProblemDetail handException(GlobalException e) { 76 | // ProblemDetail problemDetail = ProblemDetail.forStatus(e.getCode()); 77 | // problemDetail.setDetail(e.getMessage()); 78 | // return problemDetail; 79 | // } 80 | } -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/config/JwtAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import com.cn.model.RestCode; 4 | import com.cn.util.JacksonUtil; 5 | import com.cn.util.Result; 6 | import org.apache.http.entity.ContentType; 7 | import org.springframework.security.core.AuthenticationException; 8 | import org.springframework.security.web.AuthenticationEntryPoint; 9 | import org.springframework.stereotype.Component; 10 | 11 | import jakarta.servlet.http.HttpServletRequest; 12 | import jakarta.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | import java.io.PrintWriter; 15 | 16 | /** 17 | * 未授权配置 18 | * @author ngcly 19 | * @since 2020/5/14 19:12 20 | * @version V1.0 21 | */ 22 | @Component 23 | public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { 24 | @Override 25 | public void commence(HttpServletRequest request, 26 | HttpServletResponse response, 27 | AuthenticationException authException) throws IOException { 28 | RestCode restCode = authException==null?RestCode.UNAUTHORIZED:RestCode.NOT_LOGIN; 29 | response.setContentType(ContentType.APPLICATION_JSON.toString()); 30 | try (PrintWriter printWriter = response.getWriter()) { 31 | String jsonStr = JacksonUtil.stringify(Result.failure(restCode)); 32 | printWriter.write(jsonStr); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/config/JwtAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import cn.hutool.core.date.DateUtil; 4 | import cn.hutool.jwt.JWT; 5 | import cn.hutool.jwt.JWTUtil; 6 | import cn.hutool.jwt.RegisteredPayload; 7 | import com.cn.entity.User; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.security.authentication.AuthenticationManager; 10 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 11 | import org.springframework.security.core.context.SecurityContextHolder; 12 | import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; 13 | import org.springframework.util.StringUtils; 14 | 15 | import jakarta.servlet.FilterChain; 16 | import jakarta.servlet.ServletException; 17 | import jakarta.servlet.http.HttpServletRequest; 18 | import jakarta.servlet.http.HttpServletResponse; 19 | import java.io.IOException; 20 | import java.time.Instant; 21 | import java.time.LocalDateTime; 22 | import java.util.Objects; 23 | 24 | /** 25 | * 请求校验 26 | * 27 | * @author chenning 28 | */ 29 | @Slf4j 30 | public class JwtAuthenticationFilter extends BasicAuthenticationFilter { 31 | private final JwtTokenUtil jwtTokenUtil; 32 | 33 | public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtTokenUtil jwtTokenUtil) { 34 | super(authenticationManager); 35 | this.jwtTokenUtil = jwtTokenUtil; 36 | } 37 | 38 | @Override 39 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { 40 | String token = JwtTokenUtil.getToken(request); 41 | if (StringUtils.hasLength(token)) { 42 | User user = null; 43 | Instant issuedAt = null; 44 | try { 45 | JWT jwt = JWTUtil.parseToken(token); 46 | if (jwtTokenUtil.verify(jwt)) { 47 | user = jwt.getPayload().getClaimsJson().toBean(User.class); 48 | issuedAt = DateUtil.toInstant(jwt.getPayloads().getDate(RegisteredPayload.ISSUED_AT)); 49 | } 50 | } catch (Exception e) { 51 | log.info("token 无效:{}", e.getMessage()); 52 | } 53 | 54 | if (validUserAuthenticated(user, issuedAt)) { 55 | UsernamePasswordAuthenticationToken authenticationToken = 56 | UsernamePasswordAuthenticationToken.authenticated(user, null, user.getAuthorities()); 57 | log.info("authenticated user:{}", authenticationToken); 58 | SecurityContextHolder.getContext().setAuthentication(authenticationToken); 59 | } 60 | } 61 | chain.doFilter(request, response); 62 | } 63 | 64 | /** 65 | * 判断是否已授权 66 | * 67 | * @param userDetail 用户信息 68 | * @param issuedAt jwt有效期 69 | * @return boolean 70 | */ 71 | public boolean validUserAuthenticated(User userDetail, Instant issuedAt) { 72 | return Objects.nonNull(userDetail) 73 | && Objects.isNull(SecurityContextHolder.getContext().getAuthentication()) 74 | && Objects.nonNull(issuedAt) 75 | && (Objects.isNull(userDetail.getPwdAlt()) || issuedAt.isAfter(userDetail.getPwdAlt())); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/config/JwtTokenUtil.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import cn.hutool.core.date.DateField; 4 | import cn.hutool.core.date.DateUtil; 5 | import cn.hutool.crypto.SecureUtil; 6 | import cn.hutool.extra.spring.SpringUtil; 7 | import cn.hutool.http.Header; 8 | import cn.hutool.jwt.JWT; 9 | import cn.hutool.jwt.JWTUtil; 10 | import com.cn.entity.User; 11 | import com.fasterxml.jackson.databind.ObjectMapper; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.stereotype.Component; 14 | import org.springframework.util.StringUtils; 15 | 16 | import jakarta.servlet.http.HttpServletRequest; 17 | import java.util.Date; 18 | import java.util.Map; 19 | 20 | /** 21 | * JWT工具类 22 | * @author ngcly 23 | * @since 2020/5/13 17:30 24 | * @version V1.0 25 | */ 26 | @Component 27 | public class JwtTokenUtil { 28 | @Value("${jwt.secret}") 29 | private String secret; 30 | @Value("${jwt.expiration}") 31 | private Integer expiration; 32 | 33 | private static final String BEARER = "Bearer "; 34 | 35 | /** 36 | * 生成 user token 37 | * @param user 用户信息 38 | * @return jwt字符串 39 | */ 40 | public String generateToken(User user) { 41 | final Date now = new Date(); 42 | ObjectMapper mapper = SpringUtil.getBean(ObjectMapper.class); 43 | Map map = mapper.convertValue(user, Map.class); 44 | //创建jwt 45 | return JWT.create() 46 | .setSubject(user.getUsername()) 47 | .addPayloads(map) 48 | .setIssuedAt(now) 49 | .setExpiresAt(DateUtil.offsetSecond(now,expiration)) 50 | .setKey(SecureUtil.md5(secret).getBytes()) 51 | .sign(); 52 | } 53 | 54 | public String generateToken(String str) { 55 | return generateToken(str,expiration); 56 | } 57 | 58 | public String generateToken(String str, Integer expTime) { 59 | return generateToken(str,expTime,DateField.SECOND); 60 | } 61 | 62 | public String generateToken(String str, Integer expTime, DateField dateField) { 63 | final Date now =new Date(); 64 | return JWT.create() 65 | .setSubject(str) 66 | .setIssuedAt(now) 67 | .setExpiresAt(DateUtil.offset(now,dateField,expTime)) 68 | .setKey(SecureUtil.md5(secret).getBytes()) 69 | .sign(); 70 | } 71 | 72 | /** 73 | * 刷新token 74 | * @param token token字符串 75 | * @return jwt字符串 76 | */ 77 | public String refreshToken(String token) { 78 | final Date now = new Date(); 79 | return JWTUtil.parseToken(token) 80 | .setIssuedAt(now) 81 | .setExpiresAt(DateUtil.offsetSecond(now,expiration)) 82 | .sign(); 83 | } 84 | 85 | /** 86 | * 验证jwt 87 | * @param jwt jwt对象 88 | * @return boolean 89 | */ 90 | public boolean verify(JWT jwt){ 91 | return jwt.setKey(SecureUtil.md5(secret).getBytes()).validate(0L); 92 | } 93 | 94 | /** 95 | * 从Http请求里获取token 96 | * @param request 请求 97 | * @return String 98 | */ 99 | public static String getToken(HttpServletRequest request){ 100 | String jwtToken = request.getHeader(Header.AUTHORIZATION.toString()); 101 | if(StringUtils.hasLength(jwtToken) && jwtToken.startsWith(BEARER)){ 102 | return jwtToken.substring(BEARER.length()); 103 | } 104 | return null; 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/config/MyAuthenticationToken.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import org.springframework.security.authentication.AbstractAuthenticationToken; 4 | import org.springframework.security.core.GrantedAuthority; 5 | import org.springframework.util.Assert; 6 | 7 | import java.util.Collection; 8 | 9 | /** 10 | * @author chenning 11 | */ 12 | public class MyAuthenticationToken extends AbstractAuthenticationToken { 13 | private final String loginType; 14 | private final Object principal; 15 | private Object credentials; 16 | 17 | 18 | /** 19 | * 认证时过滤器通过这个方法创建Token,传入前端的参数 20 | * This constructor can be safely used by any code that wishes to create a 21 | * UsernamePasswordAuthenticationToken, as the {@link #isAuthenticated()} 22 | * will return false. 23 | * 24 | */ 25 | public MyAuthenticationToken(String loginType, Object principal, Object credentials) { 26 | super(null); 27 | this.loginType = loginType; 28 | this.principal = principal; 29 | this.credentials = credentials; 30 | setAuthenticated(false); 31 | } 32 | 33 | /** 34 | * 认证通过后Provider通过这个方法创建Token,传入自定义信息以及授权信息 35 | * This constructor should only be used by AuthenticationManager or 36 | * AuthenticationProvider implementations that are satisfied with 37 | * producing a trusted (i.e. {@link #isAuthenticated()} = true) 38 | * authentication token. 39 | * @param principal 40 | * @param credentials 41 | * @param authorities 42 | */ 43 | public MyAuthenticationToken(String loginType, Object principal, Object credentials, 44 | Collection authorities) { 45 | super(authorities); 46 | this.loginType = loginType; 47 | this.principal = principal; 48 | this.credentials = credentials; 49 | super.setAuthenticated(true); // must use super, as we override 50 | } 51 | 52 | public String getLoginType() { 53 | return loginType; 54 | } 55 | 56 | @Override 57 | public Object getCredentials() { 58 | return this.credentials; 59 | } 60 | 61 | @Override 62 | public Object getPrincipal() { 63 | return this.principal; 64 | } 65 | 66 | @Override 67 | public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { 68 | Assert.isTrue(!isAuthenticated, 69 | "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); 70 | super.setAuthenticated(false); 71 | } 72 | 73 | @Override 74 | public void eraseCredentials() { 75 | super.eraseCredentials(); 76 | this.credentials = null; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/config/SocialAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import com.cn.UserService; 4 | import com.cn.entity.User; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.security.authentication.AuthenticationProvider; 7 | import org.springframework.security.core.Authentication; 8 | import org.springframework.security.core.AuthenticationException; 9 | import org.springframework.util.Assert; 10 | 11 | import java.util.ArrayList; 12 | 13 | /** 14 | * @author chenning 15 | */ 16 | @RequiredArgsConstructor 17 | public class SocialAuthenticationProvider implements AuthenticationProvider { 18 | private final UserService userService; 19 | 20 | @Override 21 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 22 | Assert.isTrue(!authentication.isAuthenticated(), "Already authenticated"); 23 | Assert.notNull(this.userService, "A UserService must be set"); 24 | Assert.isInstanceOf(MyAuthenticationToken.class, authentication, 25 | () -> "Only MyAuthenticationToken is supported"); 26 | //获取过滤器封装的token信息 27 | MyAuthenticationToken token = (MyAuthenticationToken) authentication; 28 | User user = userService.socialLogin(token.getLoginType(), token.getPrincipal().toString(), token.getCredentials().toString()); 29 | 30 | return new MyAuthenticationToken(token.getLoginType(), user.getUsername(), user.getPassword(), new ArrayList<>()); 31 | } 32 | 33 | @Override 34 | public boolean supports(Class authentication) { 35 | return MyAuthenticationToken.class.isAssignableFrom(authentication); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import io.swagger.v3.oas.models.Components; 4 | import io.swagger.v3.oas.models.ExternalDocumentation; 5 | import io.swagger.v3.oas.models.OpenAPI; 6 | import io.swagger.v3.oas.models.info.Info; 7 | import io.swagger.v3.oas.models.info.License; 8 | import io.swagger.v3.oas.models.security.SecurityRequirement; 9 | import io.swagger.v3.oas.models.security.SecurityScheme; 10 | import org.springdoc.core.models.GroupedOpenApi; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | 14 | /** 15 | * @author ngcly 16 | */ 17 | @Configuration 18 | public class SwaggerConfig { 19 | 20 | @Bean 21 | public OpenAPI openApi() { 22 | return new OpenAPI() 23 | .components(new Components().addSecuritySchemes("bearer-key", 24 | new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT"))) 25 | .addSecurityItem(new SecurityRequirement().addList("bearer-key")) 26 | .info(new Info().title("MusicStory API") 27 | .description("Music and story application") 28 | .version("v1.0") 29 | .license(new License().name("Apache 2.0").url("https://springdoc.org"))) 30 | .externalDocs(new ExternalDocumentation() 31 | .description("Music Story Wiki Documentation") 32 | .url("https://ngcly.cn")); 33 | } 34 | 35 | @Bean 36 | public GroupedOpenApi defaultApi(){ 37 | return GroupedOpenApi.builder() 38 | .group("Default") 39 | .packagesToScan("com.cn.controller") 40 | .build(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/config/UnauthorizedEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import com.cn.model.RestCode; 4 | import com.cn.util.JacksonUtil; 5 | import com.cn.util.Result; 6 | import org.apache.http.entity.ContentType; 7 | import org.springframework.security.authentication.*; 8 | import org.springframework.security.core.AuthenticationException; 9 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 10 | import org.springframework.security.web.AuthenticationEntryPoint; 11 | import org.springframework.stereotype.Component; 12 | 13 | import jakarta.servlet.http.HttpServletRequest; 14 | import jakarta.servlet.http.HttpServletResponse; 15 | import java.io.IOException; 16 | import java.io.PrintWriter; 17 | 18 | /** 19 | * 自定义未认证 401 返回值 20 | * @author ngcly 21 | * @date 2018-03-01 11:03 22 | */ 23 | @Component 24 | public class UnauthorizedEntryPoint implements AuthenticationEntryPoint { 25 | @Override 26 | public void commence(HttpServletRequest request, 27 | HttpServletResponse response, 28 | AuthenticationException authException) throws IOException { 29 | response.setContentType(ContentType.APPLICATION_JSON.toString()); 30 | 31 | RestCode restCode; 32 | if (authException instanceof BadCredentialsException || 33 | authException instanceof UsernameNotFoundException) { 34 | restCode = RestCode.USER_ERR; 35 | } else if(authException instanceof AuthenticationServiceException){ 36 | restCode = RestCode.VERIFY_CODE_ERR; 37 | } else if (authException instanceof LockedException) { 38 | restCode = RestCode.USER_LOCKED; 39 | } else if (authException instanceof AccountExpiredException) { 40 | restCode = RestCode.USER_EXPIRE; 41 | } else if (authException instanceof CredentialsExpiredException) { 42 | restCode = RestCode.PASSWORD_EXPIRE; 43 | } else if (authException instanceof DisabledException) { 44 | restCode = RestCode.USER_DISABLE; 45 | } else if (authException instanceof InsufficientAuthenticationException){ 46 | restCode = RestCode.TOKEN_EXPIRE; 47 | } else { 48 | restCode = RestCode.UNAUTHORIZED; 49 | } 50 | 51 | try (PrintWriter printWriter = response.getWriter()) { 52 | String jsonStr = JacksonUtil.stringify(Result.failure(restCode)); 53 | printWriter.write(jsonStr); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/config/WebSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import com.cn.UserService; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.context.ApplicationEventPublisher; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.security.authentication.*; 9 | import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 10 | import org.springframework.security.config.Customizer; 11 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 12 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 13 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 14 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 15 | import org.springframework.security.config.http.SessionCreationPolicy; 16 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 17 | import org.springframework.security.crypto.password.PasswordEncoder; 18 | import org.springframework.security.web.SecurityFilterChain; 19 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 20 | 21 | /** 22 | * Spring Security 配置 23 | * 24 | * @author ngcly 25 | * @date 2018-02-28 16:23 26 | */ 27 | @Configuration 28 | @EnableWebSecurity 29 | @RequiredArgsConstructor 30 | @EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true) 31 | public class WebSecurityConfig { 32 | private final JwtTokenUtil jwtTokenUtil; 33 | private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; 34 | 35 | @Bean 36 | public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authManager) throws Exception { 37 | http 38 | .authorizeHttpRequests(authorizeHttpRequests -> 39 | authorizeHttpRequests 40 | .requestMatchers("/user/**").authenticated() 41 | .anyRequest().permitAll()) 42 | .cors(Customizer.withDefaults()) 43 | .csrf(AbstractHttpConfigurer::disable) 44 | .addFilterBefore(new JwtLoginFilter(authManager, jwtTokenUtil), UsernamePasswordAuthenticationFilter.class) 45 | .addFilter(new JwtAuthenticationFilter(authManager, jwtTokenUtil)) 46 | .sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) 47 | .exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint(jwtAuthenticationEntryPoint)); 48 | return http.build(); 49 | } 50 | 51 | @Bean 52 | public PasswordEncoder passwordEncoder() { 53 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 54 | } 55 | 56 | @Bean 57 | public AuthenticationManager providerManager(UserService userService, PasswordEncoder passwordEncoder, 58 | ApplicationEventPublisher applicationEventPublisher){ 59 | DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); 60 | authenticationProvider.setUserDetailsService(userService); 61 | authenticationProvider.setPasswordEncoder(passwordEncoder); 62 | SocialAuthenticationProvider myAuthenticationProvider = new SocialAuthenticationProvider(userService); 63 | ProviderManager providerManager = new ProviderManager(authenticationProvider, myAuthenticationProvider); 64 | providerManager.setAuthenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher)); 65 | return providerManager; 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/constant/LoginType.java: -------------------------------------------------------------------------------- 1 | package com.cn.constant; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | /** 6 | * @author chenning 7 | */ 8 | public enum LoginType { 9 | @JsonProperty("username") 10 | USERNAME, 11 | @JsonProperty("mobile") 12 | MOBILE, 13 | @JsonProperty("wechat") 14 | WECHAT, 15 | @JsonProperty("qq") 16 | QQ; 17 | } 18 | -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/model/EssayDTO.java: -------------------------------------------------------------------------------- 1 | package com.cn.model; 2 | 3 | import com.cn.entity.Music; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import lombok.Data; 6 | 7 | import jakarta.validation.constraints.NotBlank; 8 | import jakarta.validation.constraints.NotNull; 9 | import java.util.List; 10 | 11 | /** 12 | * @author ngcly 13 | */ 14 | @Data 15 | @Schema(title="创建文章参数", description = "创建文章需要参数") 16 | public class EssayDTO { 17 | 18 | @Schema(title="主键") 19 | private String id; 20 | 21 | @Schema(title="标题", requiredMode = Schema.RequiredMode.REQUIRED) 22 | @NotBlank(message = "标题不可为空") 23 | private String title; 24 | 25 | @Schema(title="分类", requiredMode = Schema.RequiredMode.REQUIRED) 26 | @NotNull(message = "分类不可为空") 27 | private Long classifyId; 28 | 29 | @Schema(title="简介", requiredMode = Schema.RequiredMode.REQUIRED) 30 | private String synopsis; 31 | 32 | @Schema(title="内容", requiredMode = Schema.RequiredMode.REQUIRED) 33 | @NotBlank(message = "内容不可为空") 34 | private String content; 35 | 36 | @Schema(title="音乐列表") 37 | private List musicList; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/model/LogInDTO.java: -------------------------------------------------------------------------------- 1 | package com.cn.model; 2 | 3 | import com.cn.constant.LoginType; 4 | import com.fasterxml.jackson.annotation.JsonSubTypes; 5 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.AccessLevel; 8 | import lombok.Getter; 9 | import lombok.Setter; 10 | 11 | import jakarta.validation.constraints.NotBlank; 12 | import jakarta.validation.constraints.NotNull; 13 | 14 | /** 15 | * 登录数据封装类 16 | * @author ngcly 17 | * @since 2018-08-05 13:42 18 | */ 19 | @Getter 20 | @Schema(title="登录参数", description = "登录需要以下参数", 21 | discriminatorProperty = "loginType", subTypes = { 22 | LogInDTO.UserNameLoginDTO.class, 23 | LogInDTO.SocialLoginDTO.class}) 24 | @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "loginType", include = JsonTypeInfo.As.EXISTING_PROPERTY, visible = true) 25 | @JsonSubTypes({ 26 | @JsonSubTypes.Type(value = LogInDTO.UserNameLoginDTO.class, name = "username"), 27 | @JsonSubTypes.Type(value = LogInDTO.SocialLoginDTO.class, names = {"qq", "wechat"}) 28 | }) 29 | public abstract class LogInDTO { 30 | @Schema(title="登录类型", requiredMode = Schema.RequiredMode.REQUIRED) 31 | @NotNull(message = "登录类型不可为空") 32 | @Setter(AccessLevel.PROTECTED) 33 | private LoginType loginType; 34 | 35 | @Schema 36 | @Getter 37 | @Setter 38 | public static class UserNameLoginDTO extends LogInDTO { 39 | @Schema(title="用户名", requiredMode = Schema.RequiredMode.REQUIRED) 40 | @NotBlank(message = "用户名不可为空") 41 | private String username; 42 | 43 | @Schema(title="密码", requiredMode = Schema.RequiredMode.REQUIRED) 44 | @NotBlank(message = "密码不可为空") 45 | private String password; 46 | } 47 | 48 | @Schema 49 | @Getter 50 | @Setter 51 | public static class SocialLoginDTO extends LogInDTO { 52 | @Schema(title="授权码", requiredMode = Schema.RequiredMode.REQUIRED) 53 | @NotBlank(message = "授权码不可为空") 54 | private String code; 55 | @Schema(title="csrf值", requiredMode = Schema.RequiredMode.REQUIRED) 56 | @NotBlank(message = "csrf值不可为空") 57 | private String state; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/model/SignUpDTO.java: -------------------------------------------------------------------------------- 1 | package com.cn.model; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Data; 5 | 6 | import jakarta.validation.constraints.Email; 7 | import jakarta.validation.constraints.NotBlank; 8 | import jakarta.validation.constraints.Size; 9 | 10 | /** 11 | * 注册参数数据封装类 12 | * @author nglcy 13 | * @since 2018-08-05 13:50 14 | */ 15 | @Data 16 | @Schema(title="注册", description = "注册参数") 17 | public class SignUpDTO { 18 | 19 | @Schema(title="用户名", requiredMode = Schema.RequiredMode.REQUIRED) 20 | @NotBlank 21 | @Size(min = 3, max = 15) 22 | private String username; 23 | 24 | @Schema(title="邮箱", requiredMode = Schema.RequiredMode.REQUIRED) 25 | @NotBlank 26 | @Size(max = 40) 27 | @Email 28 | private String email; 29 | 30 | @Schema(title="手机号") 31 | private String phone; 32 | 33 | @Schema(title="密码", requiredMode = Schema.RequiredMode.REQUIRED) 34 | @NotBlank 35 | @Size(min = 6, max = 32) 36 | private String password; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /interface/src/main/java/com/cn/model/UserVO.java: -------------------------------------------------------------------------------- 1 | package com.cn.model; 2 | 3 | import com.cn.enums.GenderEnum; 4 | import com.cn.enums.UserStatusEnum; 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.Data; 7 | 8 | import java.math.BigDecimal; 9 | import java.time.LocalDate; 10 | 11 | /** 12 | * 用户展示数据封装类 13 | * @author ngcly 14 | */ 15 | @Data 16 | @Schema(title="用户", description = "用户信息") 17 | public class UserVO { 18 | @Schema(title = "用户名") 19 | private String username; 20 | @Schema(title = "昵称") 21 | private String nickName; 22 | @Schema(title = "生日") 23 | private LocalDate birthday; 24 | @Schema(title = "性别") 25 | private GenderEnum gender ; 26 | @Schema(title = "地址") 27 | private String address; 28 | @Schema(title = "真实姓名") 29 | private String realName; 30 | @Schema(title = "个人简介") 31 | private String personDesc; 32 | @Schema(title = "个性签名") 33 | private String signature; 34 | @Schema(title = "头像") 35 | private String avatar; 36 | @Schema(title = "手机号") 37 | private String phone; 38 | @Schema(title = "邮箱地址") 39 | private String email; 40 | @Schema(title = "账户余额") 41 | private BigDecimal balance; 42 | @Schema(title = "等级") 43 | private Integer level; 44 | @Schema(title = "积分") 45 | private Integer credit; 46 | @Schema(title = "用户状态") 47 | private UserStatusEnum state; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /interface/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8070 3 | # 开启 gzip 压缩 4 | compression: 5 | enabled: true 6 | # 开启 http2 支持 只在https下起作用 7 | http2: 8 | enabled: true 9 | 10 | spring: 11 | jpa: 12 | database: MYSQL 13 | show-sql: false 14 | generate-ddl: true 15 | hibernate: 16 | ddl-auto: update 17 | 18 | #数据源 19 | datasource: 20 | url: jdbc:mysql://localhost:3306/music_story?serverTimezone=UTC&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true 21 | username: root 22 | password: 12345678 23 | driverClassName: com.mysql.cj.jdbc.Driver 24 | type: com.alibaba.druid.pool.DruidDataSource 25 | # 下面为连接池的补充设置,应用到上面所有数据源中 26 | druid: 27 | # 初始化大小,最小,最大 28 | initialSize: 5 29 | minIdle: 5 30 | maxActive: 20 31 | # 配置获取连接等待超时的时间 32 | maxWait: 60000 33 | validationQuery: SELECT 1 34 | # 打开PSCache,并且指定每个连接上PSCache的大小 35 | poolPreparedStatements: true 36 | maxPoolPreparedStatementPerConnectionSize: 20 37 | # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall' 用于防火墙 38 | filter: 39 | stat: 40 | merge-sql: true 41 | db-type: mysql 42 | slow-sql-millis: 1000 43 | log-slow-sql: true 44 | #配置 wall filter 45 | wall: 46 | db-type: mysql 47 | # 合并多个DruidDataSource的监控数据 48 | useGlobalDataSourceStat: true 49 | 50 | jackson: 51 | date-format: yyyy-MM-dd HH:mm:ss 52 | 53 | #邮件配置 54 | mail: 55 | host: smtp.qq.com 56 | port: 465 57 | username: 531237716@qq.com 58 | password: ENC(pS7NvCDk4ouixF9t8kcIPfrRUr/ZZ3J+JGBYc5yT4TAGHb8YBLu7r3l2ZSWoPuFIy7ROpcPHZCP/gYBTHWpNvw==) 59 | properties: 60 | mail: 61 | smtp: 62 | auth: true 63 | starttls: 64 | enable: true 65 | required: true 66 | ssl: 67 | enable: true 68 | 69 | jwt: 70 | secret: ngcly 71 | expiration: 3600 72 | 73 | #私人密钥配置 74 | oss: 75 | config: 76 | host: https://oss-cn-hongkong-internal.aliyuncs.com 77 | bucketName: music-story 78 | accessKeyId: ENC(BFmGqMfhERt8BVG81BuQFAI4L165TxyjahwvlTGx9QnjkKAWytNvhhtfiajw1mhsGTLh5JErMOFrjltogIkOUg==) 79 | accessKeySecret: ENC(Ng+yTJPV6RPseJCbuVbk2vqwElnl5fdddl22TAYCvyOVoHSjINi9Tk01j+8CLmozS/ysp3gWFbU5Ock4znKENw==) 80 | endpoint: oss.ngcly.cn 81 | 82 | springdoc: 83 | packagesToScan: com.cn.controller 84 | pathsToMatch: /** 85 | show-actuator: true 86 | swagger-ui: 87 | enabled: true -------------------------------------------------------------------------------- /interface/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ## ## ## ## ###### #### ###### 2 | ### ### ## ## ## ## ## ## ## 3 | #### #### ## ## ## ## ## 4 | ## ### ## ## ## ###### ## ## 5 | ## ## ## ## ## ## ## 6 | ## ## ## ## ## ## ## ## ## 7 | ## ## ####### ###### #### ###### 8 | ###### ######## ####### ######## ## ## 9 | ## ## ## ## ## ## ## ## ## 10 | ## ## ## ## ## ## #### 11 | ###### ## ## ## ######## ## 12 | ## ## ## ## ## ## ## 13 | ## ## ## ## ## ## ## ## 14 | ###### ## ####### ## ## ## -------------------------------------------------------------------------------- /interface/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ${APP_NAME} 10 | 11 | 12 | 13 | ${LOG_FILE_PATH}/${APP_NAME}-%d{yyyy-MM-dd}.log 14 | 30 15 | 16 | 17 | ${FILE_LOG_PATTERN} 18 | 19 | 20 | 21 | 22 | 23 | localhost:4560 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /jetbrains.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngcly/MusicStory/183675ed994f9037033e8406bb3999c4b43aa2a9/jetbrains.png -------------------------------------------------------------------------------- /logstash.conf: -------------------------------------------------------------------------------- 1 | input { 2 | tcp { 3 | mode => "server" 4 | host => "0.0.0.0" 5 | port => 4560 6 | codec => json_lines 7 | } 8 | } 9 | output { 10 | elasticsearch { 11 | hosts => "es:9200" 12 | index => "springboot-logstash-%{+YYYY.MM.dd}" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /logstash/logstash.conf: -------------------------------------------------------------------------------- 1 | input { 2 | tcp { 3 | mode => "server" 4 | host => "0.0.0.0" 5 | port => 4560 6 | codec => json_lines 7 | } 8 | } 9 | output { 10 | elasticsearch { 11 | hosts => "es:9200" 12 | index => "springboot-logstash-%{+YYYY.MM.dd}" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /repository/build.gradle: -------------------------------------------------------------------------------- 1 | jar { 2 | enabled = true 3 | } 4 | bootJar { 5 | enabled = false 6 | } 7 | 8 | dependencies { 9 | api project(":commons") 10 | api 'org.springframework.boot:spring-boot-starter-data-jpa' 11 | api 'org.springframework.boot:spring-boot-starter-data-elasticsearch' 12 | api "com.alibaba:druid-spring-boot-3-starter:${druidVersion}" 13 | 14 | runtimeOnly 'com.h2database:h2' 15 | runtimeOnly 'com.mysql:mysql-connector-j' 16 | } 17 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/config/AbstractDateAudit.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import jakarta.persistence.EntityListeners; 5 | import jakarta.persistence.MappedSuperclass; 6 | import org.springframework.data.annotation.CreatedDate; 7 | import org.springframework.data.annotation.LastModifiedDate; 8 | import org.springframework.data.annotation.Version; 9 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 10 | import java.io.Serializable; 11 | import java.time.Instant; 12 | 13 | /** 14 | * @author ngcly 15 | */ 16 | @MappedSuperclass 17 | @EntityListeners(AuditingEntityListener.class) 18 | @JsonIgnoreProperties( 19 | value = {"createdAt", "updatedAt"}, 20 | allowGetters = true 21 | ) 22 | public abstract class AbstractDateAudit implements Serializable { 23 | 24 | /** 创建时间 */ 25 | @CreatedDate 26 | private Instant createdAt; 27 | 28 | /** 修改时间 */ 29 | @LastModifiedDate 30 | private Instant updatedAt; 31 | 32 | @Version 33 | private Long version; 34 | 35 | public Instant getCreatedAt() { 36 | return createdAt; 37 | } 38 | 39 | public void setCreatedAt(Instant createdAt) { 40 | this.createdAt = createdAt; 41 | } 42 | 43 | public Instant getUpdatedAt() { 44 | return updatedAt; 45 | } 46 | 47 | public void setUpdatedAt(Instant updatedAt) { 48 | this.updatedAt = updatedAt; 49 | } 50 | } -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/config/AbstractUserDateAudit.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import jakarta.persistence.Column; 5 | import jakarta.persistence.MappedSuperclass; 6 | import org.springframework.data.annotation.CreatedBy; 7 | import org.springframework.data.annotation.LastModifiedBy; 8 | 9 | /** 10 | * @author ngcly 11 | */ 12 | @MappedSuperclass 13 | @JsonIgnoreProperties( 14 | value = {"createdBy", "updatedBy"}, 15 | allowGetters = true 16 | ) 17 | public abstract class AbstractUserDateAudit extends AbstractDateAudit { 18 | 19 | /** 创建人 */ 20 | @CreatedBy 21 | @Column(nullable = false,updatable = false, length = 32) 22 | private String createdBy; 23 | 24 | /** 修改建人 */ 25 | @LastModifiedBy 26 | @Column(nullable = false,length = 32) 27 | private String updatedBy; 28 | 29 | public String getCreatedBy() { 30 | return createdBy; 31 | } 32 | 33 | public void setCreatedBy(String createdBy) { 34 | this.createdBy = createdBy; 35 | } 36 | 37 | public String getUpdatedBy() { 38 | return updatedBy; 39 | } 40 | 41 | public void setUpdatedBy(String updatedBy) { 42 | this.updatedBy = updatedBy; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/config/AuditingConfig.java: -------------------------------------------------------------------------------- 1 | package com.cn.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.data.domain.AuditorAware; 5 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.security.core.context.SecurityContext; 8 | import org.springframework.security.core.context.SecurityContextHolder; 9 | 10 | import java.util.Optional; 11 | 12 | /** 13 | * @author ngcly 14 | */ 15 | @Configuration 16 | @EnableJpaAuditing 17 | public class AuditingConfig implements AuditorAware { 18 | 19 | @Override 20 | public Optional getCurrentAuditor() { 21 | return Optional.ofNullable(SecurityContextHolder.getContext()) 22 | .map(SecurityContext::getAuthentication) 23 | .filter(Authentication::isAuthenticated) 24 | .map(Authentication::getName); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/dao/BookRepository.java: -------------------------------------------------------------------------------- 1 | package com.cn.dao; 2 | 3 | import com.cn.entity.Book; 4 | import org.springframework.data.domain.Pageable; 5 | import org.springframework.data.elasticsearch.annotations.Highlight; 6 | import org.springframework.data.elasticsearch.annotations.HighlightField; 7 | import org.springframework.data.elasticsearch.annotations.HighlightParameters; 8 | import org.springframework.data.elasticsearch.core.SearchHit; 9 | import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * @author ngcly 15 | */ 16 | public interface BookRepository extends ElasticsearchRepository { 17 | 18 | /** 19 | * 从ES搜索文章 20 | * 21 | * @param title 标题 22 | * @param content 内容 23 | * @param pageable 分页 24 | * @return Page 25 | */ 26 | @Highlight( 27 | fields = { 28 | @HighlightField(name = "title"), 29 | @HighlightField(name = "content") 30 | }, 31 | parameters = @HighlightParameters( 32 | preTags = "", 33 | postTags = "" 34 | ) 35 | ) 36 | List> findBooksByTitleOrContent(String title, String content, Pageable pageable); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/dao/CarouselRepository.java: -------------------------------------------------------------------------------- 1 | package com.cn.dao; 2 | 3 | import com.cn.entity.CarouselCategory; 4 | import org.springframework.data.jpa.domain.Specification; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 7 | import org.springframework.data.jpa.repository.Modifying; 8 | import org.springframework.data.jpa.repository.Query; 9 | import org.springframework.data.repository.query.Param; 10 | import org.springframework.stereotype.Repository; 11 | import org.springframework.util.StringUtils; 12 | 13 | import jakarta.persistence.criteria.CriteriaBuilder; 14 | import jakarta.persistence.criteria.CriteriaQuery; 15 | import jakarta.persistence.criteria.Predicate; 16 | import jakarta.persistence.criteria.Root; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | /** 21 | * @author ngcly 22 | */ 23 | @Repository 24 | public interface CarouselRepository extends JpaRepository, JpaSpecificationExecutor { 25 | 26 | /** 27 | * 动态获取轮播图分类 28 | * @param name 名称 29 | * @return Specification 30 | */ 31 | static Specification getCarouselCategory(String name){ 32 | return (Root root, CriteriaQuery query, CriteriaBuilder cb)->{ 33 | List predicates = new ArrayList<>(); 34 | if(StringUtils.hasLength(name)) { 35 | predicates.add(cb.like(root.get("title"),"%"+name+"%")); 36 | } 37 | return query.where(cb.and(predicates.toArray(new Predicate[0]))).getRestriction(); 38 | }; 39 | } 40 | 41 | /** 42 | * 删除轮播图分类 43 | * @param id 主键 44 | */ 45 | @Modifying 46 | @Query(value = "delete from carousel where id=:id",nativeQuery = true) 47 | void deleteCarousel(@Param("id") Long id); 48 | } 49 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/dao/ClassifyRepository.java: -------------------------------------------------------------------------------- 1 | package com.cn.dao; 2 | 3 | import com.cn.entity.Classify; 4 | import org.springframework.data.jpa.domain.Specification; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 7 | import org.springframework.stereotype.Repository; 8 | import org.springframework.util.StringUtils; 9 | 10 | import jakarta.persistence.criteria.CriteriaBuilder; 11 | import jakarta.persistence.criteria.CriteriaQuery; 12 | import jakarta.persistence.criteria.Predicate; 13 | import jakarta.persistence.criteria.Root; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * @author ngcly 19 | */ 20 | @Repository 21 | public interface ClassifyRepository extends JpaRepository, JpaSpecificationExecutor { 22 | 23 | /** 24 | * 动态查询分类数据 25 | * @param name 分类名 26 | * @return Specification 27 | */ 28 | static Specification getClassifyList(String name){ 29 | return (Root root, CriteriaQuery query, CriteriaBuilder cb)->{ 30 | List predicates = new ArrayList<>(); 31 | if(StringUtils.hasLength(name)) { 32 | predicates.add(cb.like(root.get("name"),"%"+name+"%")); 33 | } 34 | return query.where(cb.and(predicates.toArray(new Predicate[0]))).getRestriction(); 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/dao/CommentRepository.java: -------------------------------------------------------------------------------- 1 | package com.cn.dao; 2 | 3 | import com.cn.entity.Comment; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.Query; 8 | import org.springframework.data.repository.query.Param; 9 | import org.springframework.stereotype.Repository; 10 | 11 | import java.util.Map; 12 | 13 | 14 | /** 15 | * @author ngcly 16 | */ 17 | @Repository 18 | public interface CommentRepository extends JpaRepository { 19 | 20 | /** 21 | * 获取评论 22 | * @param essayId 文章id 23 | * @param pageable 分页 24 | * @return Page 25 | */ 26 | Page findByEssayIdOrderByCreatedAtDesc(Long essayId, Pageable pageable); 27 | 28 | /** 29 | * 查询评论 30 | * @param essayId 文章id 31 | * @param pageable 分页 32 | * @return Page> 33 | */ 34 | @Query("select new map(t2.id as userId,t2.username as username,t2.avatar as avatar,t1.id as id," + 35 | "t1.createdAt as createdAt,t1.replyUserId as replyUserId,t1.content as content)" + 36 | " from Comment t1 inner join User t2 on t1.userId=t2.id where t1.essayId=:essayId") 37 | Page> selectComments(@Param("essayId") Long essayId, Pageable pageable); 38 | } 39 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/dao/EssayRepository.java: -------------------------------------------------------------------------------- 1 | package com.cn.dao; 2 | 3 | import com.cn.entity.Classify; 4 | import com.cn.entity.Essay; 5 | import com.cn.entity.User; 6 | import com.cn.enums.FaveTypeEnum; 7 | import org.springframework.data.domain.Page; 8 | import org.springframework.data.domain.Pageable; 9 | import org.springframework.data.jpa.domain.Specification; 10 | import org.springframework.data.jpa.repository.JpaRepository; 11 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 12 | import org.springframework.data.jpa.repository.Modifying; 13 | import org.springframework.data.jpa.repository.Query; 14 | import org.springframework.data.repository.query.Param; 15 | import org.springframework.stereotype.Repository; 16 | import org.springframework.util.StringUtils; 17 | 18 | import jakarta.persistence.criteria.*; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.Objects; 22 | 23 | /** 24 | * @author ngcly 25 | */ 26 | @Repository 27 | public interface EssayRepository extends JpaRepository, JpaSpecificationExecutor { 28 | 29 | /** 30 | * 删除用户的文章 31 | * @param id 文章id 32 | * @param userId 用户id 33 | * @return 影响行数 34 | */ 35 | int deleteEssayByIdAndUserId(Long id,Long userId); 36 | 37 | /** 38 | * 动态查询文章数据 39 | * @param essay 查询条件 40 | * @return Specification 文章列表 41 | */ 42 | static Specification getEssayList(Essay essay){ 43 | return (Root root, CriteriaQuery query, CriteriaBuilder cb)->{ 44 | List predicates = new ArrayList<>(); 45 | if(StringUtils.hasLength(essay.getTitle())) { 46 | predicates.add(cb.like(root.get("title"),"%"+essay.getTitle()+"%")); 47 | } 48 | if(Objects.nonNull(essay.getClassify()) && Objects.nonNull(essay.getClassify().getId())) { 49 | Join join = root.join("classify", JoinType.INNER); 50 | predicates.add(cb.equal(join.get("id"),essay.getClassify().getId())); 51 | } 52 | if(Objects.nonNull(essay.getState())) { 53 | predicates.add(cb.equal(root.get("state"), essay.getState())); 54 | } 55 | if(Objects.nonNull(essay.getUser()) && StringUtils.hasLength(essay.getUser().getUsername())){ 56 | Join join = root.join("user", JoinType.INNER); 57 | predicates.add(cb.like(join.get("username"),"%"+essay.getUser().getUsername()+"%")); 58 | } 59 | return query.where(cb.and(predicates.toArray(new Predicate[0]))).getRestriction(); 60 | }; 61 | } 62 | 63 | /** 64 | * 文章阅读数+1 65 | * @param id 文章id 66 | * @return int 影响行数 67 | */ 68 | @Modifying 69 | @Query("update Essay set readNum=readNum+1 where id=:id") 70 | int readOne(@Param("id") Long id); 71 | 72 | /** 73 | * 查询用户 收藏/点赞 文章 74 | * @param userId 用户id 75 | * @param faveType 收藏/点赞类型 76 | * @param pageable 分页 77 | * @return Page 分页文章列表 78 | */ 79 | @Query("select e from Essay e, UserFaves u where u.userId =:userId and u.essayId = e.id and u.faveType=:faveType") 80 | Page findUserFaveEssay(@Param("userId") Long userId, @Param("faveType") FaveTypeEnum faveType, Pageable pageable); 81 | 82 | /** 83 | * 获取用户的文章 84 | * @param userId 用户id 85 | * @param pageable 分页 86 | * @return Page 87 | */ 88 | Page findEssayByUserId(@Param("userId") Long userId,Pageable pageable); 89 | } 90 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/dao/LoginLogRepository.java: -------------------------------------------------------------------------------- 1 | package com.cn.dao; 2 | 3 | import com.cn.entity.LoginLog; 4 | import com.cn.enums.UserTypeEnum; 5 | import org.springframework.data.jpa.domain.Specification; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 8 | import org.springframework.stereotype.Repository; 9 | import org.springframework.util.StringUtils; 10 | 11 | import jakarta.persistence.criteria.CriteriaBuilder; 12 | import jakarta.persistence.criteria.CriteriaQuery; 13 | import jakarta.persistence.criteria.Predicate; 14 | import jakarta.persistence.criteria.Root; 15 | 16 | import java.time.Instant; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Objects; 20 | 21 | /** 22 | * 日志操作 23 | * 24 | * @author chen 25 | * @date 2018-01-02 18:55 26 | */ 27 | @Repository 28 | public interface LoginLogRepository extends JpaRepository,JpaSpecificationExecutor { 29 | 30 | /** 31 | * 动态查询登录日志数据 32 | * @param userName 用户名 33 | * @param userType 用户类型 34 | * @param beginTime 开始时间 35 | * @param endTime 截止时间 36 | * @return Specification 37 | */ 38 | static Specification getLoginLogList(String userName, UserTypeEnum userType, Instant beginTime, Instant endTime){ 39 | return (Root root, CriteriaQuery query, CriteriaBuilder cb)->{ 40 | List predicates = new ArrayList<>(); 41 | if(StringUtils.hasLength(userName)) { 42 | predicates.add(cb.like(root.get("userName"),"%"+userName+"%")); 43 | } 44 | 45 | if(Objects.nonNull(userType)) { 46 | predicates.add(cb.equal(root.get("userType"), userType)); 47 | } 48 | 49 | if(Objects.nonNull(beginTime)){ 50 | predicates.add(cb.greaterThanOrEqualTo(root.get("loginTime"), beginTime)); 51 | } 52 | 53 | if(Objects.nonNull(endTime)){ 54 | predicates.add(cb.lessThanOrEqualTo(root.get("loginTime"), endTime)); 55 | } 56 | return query.where(cb.and(predicates.toArray(new Predicate[0]))).getRestriction(); 57 | }; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/dao/ManagerRepository.java: -------------------------------------------------------------------------------- 1 | package com.cn.dao; 2 | 3 | import com.cn.entity.Manager; 4 | import com.cn.enums.GenderEnum; 5 | import com.cn.enums.UserStatusEnum; 6 | import org.springframework.data.jpa.domain.Specification; 7 | import org.springframework.data.jpa.repository.EntityGraph; 8 | import org.springframework.data.jpa.repository.JpaRepository; 9 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 10 | import org.springframework.stereotype.Repository; 11 | import org.springframework.util.StringUtils; 12 | 13 | import jakarta.persistence.criteria.CriteriaBuilder; 14 | import jakarta.persistence.criteria.CriteriaQuery; 15 | import jakarta.persistence.criteria.Predicate; 16 | import jakarta.persistence.criteria.Root; 17 | import java.time.LocalDateTime; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.Objects; 21 | import java.util.Optional; 22 | 23 | /** 24 | * @author ngcly 25 | * @since 2018-01-02 17:11 26 | */ 27 | @Repository 28 | public interface ManagerRepository extends JpaRepository,JpaSpecificationExecutor { 29 | @EntityGraph(value = "Role.Graph", type = EntityGraph.EntityGraphType.FETCH) 30 | Optional findManagerById(Long id); 31 | 32 | /** 33 | * 根据用户名查询 34 | * @param username 用户名 35 | * @return Manager 36 | */ 37 | @EntityGraph(value = "Role.Graph", type = EntityGraph.EntityGraphType.FETCH) 38 | Optional findManagerByUsername(String username); 39 | 40 | /** 41 | * 判断用户名是否存在 42 | * @param username 用户名 43 | * @param userId 用户id 44 | * @return boolean 45 | */ 46 | boolean existsByUsernameAndIdIsNot(String username,Long userId); 47 | 48 | /** 49 | * 动态查询管理员数据 50 | * @param username 用户名 51 | * @param state 状态 52 | * @param gender 性别 53 | * @param beginTime 开始时间 54 | * @param endTime 结束时间 55 | * @return Specification 56 | */ 57 | static Specification getManagerList(String username, UserStatusEnum state, GenderEnum gender, LocalDateTime beginTime, LocalDateTime endTime){ 58 | return (Root root, CriteriaQuery query, CriteriaBuilder cb)->{ 59 | List predicates = new ArrayList<>(); 60 | if(StringUtils.hasLength(username)) { 61 | predicates.add(cb.like(root.get("username"),"%"+username+"%")); 62 | } 63 | 64 | if(Objects.nonNull(state)) { 65 | predicates.add(cb.equal(root.get("state"), state)); 66 | } 67 | 68 | if(Objects.nonNull(gender)) { 69 | predicates.add(cb.equal(root.get("gender"), gender)); 70 | } 71 | 72 | if(Objects.nonNull(beginTime)){ 73 | predicates.add(cb.greaterThanOrEqualTo(root.get("createdAt"), beginTime)); 74 | } 75 | 76 | if(Objects.nonNull(endTime)){ 77 | predicates.add(cb.lessThanOrEqualTo(root.get("createdAt"), endTime)); 78 | } 79 | return query.where(cb.and(predicates.toArray(new Predicate[0]))).getRestriction(); 80 | }; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/dao/NoticeRepository.java: -------------------------------------------------------------------------------- 1 | package com.cn.dao; 2 | 3 | import com.cn.entity.Notice; 4 | import org.springframework.data.jpa.domain.Specification; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 7 | import org.springframework.stereotype.Repository; 8 | import org.springframework.util.StringUtils; 9 | 10 | import jakarta.persistence.criteria.CriteriaBuilder; 11 | import jakarta.persistence.criteria.CriteriaQuery; 12 | import jakarta.persistence.criteria.Predicate; 13 | import jakarta.persistence.criteria.Root; 14 | 15 | import java.time.Instant; 16 | import java.time.LocalDateTime; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Objects; 20 | 21 | /** 22 | * @author ngcly 23 | */ 24 | @Repository 25 | public interface NoticeRepository extends JpaRepository, JpaSpecificationExecutor { 26 | /** 27 | * 获取展示公告 28 | * @param beginTime 开始时间 29 | * @param endTime 结束时间 30 | * @return List 31 | */ 32 | List getNoticesByBeginTimeBeforeAndEndTimeAfterOrderByCreatedAtDesc(LocalDateTime beginTime,LocalDateTime endTime); 33 | 34 | /** 35 | * 动态查询公告 36 | * @param title 标题 37 | * @param showTime 展示时间 38 | * @return Specification 39 | */ 40 | static Specification getNoticeList(String title, Instant showTime){ 41 | return (Root root, CriteriaQuery query, CriteriaBuilder cb)->{ 42 | List predicates = new ArrayList<>(); 43 | if(StringUtils.hasLength(title)) { 44 | predicates.add(cb.like(root.get("title"),"%"+title+"%")); 45 | } 46 | if(Objects.nonNull(showTime)){ 47 | predicates.add(cb.lessThanOrEqualTo(root.get("beginTime"), showTime)); 48 | predicates.add(cb.greaterThanOrEqualTo(root.get("endTime"), showTime)); 49 | } 50 | return query.where(cb.and(predicates.toArray(new Predicate[0]))).getRestriction(); 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/dao/PermissionRepository.java: -------------------------------------------------------------------------------- 1 | package com.cn.dao; 2 | 3 | import com.cn.entity.Permission; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @author ngcly 12 | * @date 2017-12-20 16:34 13 | */ 14 | @Repository 15 | public interface PermissionRepository extends JpaRepository { 16 | 17 | /** 18 | * 获取菜单列表 19 | * @return List 20 | */ 21 | @Query("select t from Permission t order by t.sort asc") 22 | List findMenuList(); 23 | 24 | /** 25 | * 根据资源类型获取资源列表 26 | * @param resourceType 资源类型 27 | * @return List 28 | */ 29 | List findAllByResourceType(String resourceType); 30 | 31 | // @Modifying 32 | // @Query("delete from Permission t where t.parentIds like :parentId%") 33 | // void deleteMenus(@Param("parentId") String parentId); 34 | 35 | /** 36 | * 删除资源 37 | * @param parentId 父级id 38 | */ 39 | void deletePermissionByParentIdsStartingWith(String parentId); 40 | 41 | } 42 | 43 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/dao/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.cn.dao; 2 | 3 | import com.cn.entity.Role; 4 | import com.cn.enums.UserTypeEnum; 5 | import org.springframework.data.jpa.domain.Specification; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 8 | import org.springframework.stereotype.Repository; 9 | import org.springframework.util.StringUtils; 10 | 11 | import jakarta.persistence.criteria.CriteriaBuilder; 12 | import jakarta.persistence.criteria.CriteriaQuery; 13 | import jakarta.persistence.criteria.Predicate; 14 | import jakarta.persistence.criteria.Root; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.Objects; 18 | 19 | /** 20 | * @author ngcly 21 | * @since 2018-03-21 18:11 22 | */ 23 | @Repository 24 | public interface RoleRepository extends JpaRepository,JpaSpecificationExecutor { 25 | 26 | /** 27 | * 根据角色类型获取所有可用角色 28 | * @param type 角色类型 29 | * @return Set 30 | */ 31 | List getAllByAvailableIsTrueAndRoleType(UserTypeEnum type); 32 | 33 | /** 34 | * 自定义条件查询 35 | * @param roleName 角色名 36 | * @param available 是否可用 37 | * @param roleType 角色类型 38 | * @return Specification 39 | */ 40 | static Specification getRoleList(String roleName, Boolean available, UserTypeEnum roleType) { 41 | return (Root root, CriteriaQuery query, CriteriaBuilder cb) -> { 42 | List predicates = new ArrayList<>(); 43 | if (StringUtils.hasLength(roleName)) { 44 | predicates.add(cb.like(root.get("roleName"), "%" + roleName + "%")); 45 | } 46 | 47 | if (Objects.nonNull(available)) { 48 | predicates.add(cb.equal(root.get("available"), available)); 49 | } 50 | 51 | if (Objects.nonNull(roleType)) { 52 | predicates.add(cb.equal(root.get("roleType"), roleType)); 53 | } 54 | 55 | return query.where(cb.and(predicates.toArray(new Predicate[0]))).getRestriction(); 56 | }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/dao/SocialInfoRepository.java: -------------------------------------------------------------------------------- 1 | package com.cn.dao; 2 | 3 | import com.cn.entity.SocialInfo; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | /** 8 | * 第三方用户 9 | * @author ngcly 10 | * @version V1.0 11 | * @since 2021/8/24 21:26 12 | */ 13 | @Repository 14 | public interface SocialInfoRepository extends JpaRepository { 15 | 16 | /** 17 | * 根据uuid 查询 18 | * @param openId 三方唯一openId 19 | * @return SocialUser 20 | */ 21 | SocialInfo findByOpenId(String openId); 22 | 23 | /** 24 | * 删除记录 25 | * @param openId 三方唯一openid 26 | */ 27 | void deleteByOpenId(String openId); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/dao/UserFavesRepository.java: -------------------------------------------------------------------------------- 1 | package com.cn.dao; 2 | 3 | import com.cn.entity.UserFaves; 4 | import com.cn.enums.FaveTypeEnum; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.stereotype.Repository; 7 | 8 | /** 9 | * @author ngcly 10 | */ 11 | @Repository 12 | public interface UserFavesRepository extends JpaRepository { 13 | 14 | /** 15 | * 删除用户 点赞/收藏 记录 16 | * 17 | * @param userId 用户id 18 | * @param essayId 文章id 19 | * @param type 类型 20 | */ 21 | void deleteUserFavesByUserIdAndEssayIdAndFaveType(long userId, long essayId, FaveTypeEnum type); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/dao/UserFollowRepository.java: -------------------------------------------------------------------------------- 1 | package com.cn.dao; 2 | 3 | import com.cn.entity.UserFollow; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | /** 7 | * @author ngcly 8 | */ 9 | public interface UserFollowRepository extends JpaRepository { 10 | 11 | /** 12 | * 根据用户id和被关注者id进行删除 13 | * @param userId 用户id 14 | * @param followId 被关注者id 15 | */ 16 | void deleteUserFollowByUserIdAndFollowId(long userId, long followId); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/dao/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.cn.dao; 2 | 3 | import com.cn.entity.User; 4 | import com.cn.enums.UserStatusEnum; 5 | import org.springframework.data.jpa.domain.Specification; 6 | import org.springframework.data.jpa.repository.EntityGraph; 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 9 | import org.springframework.stereotype.Repository; 10 | import org.springframework.util.StringUtils; 11 | 12 | import jakarta.persistence.criteria.CriteriaBuilder; 13 | import jakarta.persistence.criteria.CriteriaQuery; 14 | import jakarta.persistence.criteria.Predicate; 15 | import jakarta.persistence.criteria.Root; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Objects; 19 | import java.util.Optional; 20 | 21 | /** 22 | * 23 | * @author ngcly 24 | * @since 2017/6/23 25 | */ 26 | @Repository 27 | //@Table(name="user") 28 | //@Qualifier("userRepository") 29 | //@CacheConfig(cacheNames = "users") 30 | public interface UserRepository extends JpaRepository, JpaSpecificationExecutor { 31 | 32 | /** 33 | * 根据用户名或邮箱查找 34 | * @param username 用户名 35 | * @param email 邮箱 36 | * @return User 37 | */ 38 | @EntityGraph(value = "UserRole.Graph", type = EntityGraph.EntityGraphType.FETCH) 39 | Optional findByUsernameOrEmail(String username, String email); 40 | 41 | /** 42 | * 根据用户名判断用户是否存在 43 | * @param username 用户名 44 | * @return boolean 45 | */ 46 | boolean existsByUsername(String username); 47 | 48 | /** 49 | * 根据邮箱判断用户是否存在 50 | * @param email 邮箱 51 | * @return boolean 52 | */ 53 | boolean existsByEmail(String email); 54 | 55 | /** 56 | * 动态查询管理员数据 57 | * @param username 用户名 58 | * @param nickName 昵称 59 | * @param phone 手机号 60 | * @param email 邮箱 61 | * @param state 状态 62 | * @return Specification getUserList(String username, String nickName, String phone, String email, UserStatusEnum state){ 65 | return (Root root, CriteriaQuery query, CriteriaBuilder cb)->{ 66 | List predicates = new ArrayList<>(); 67 | if(StringUtils.hasLength(username)) { 68 | predicates.add(cb.like(root.get("username"),"%"+username+"%")); 69 | } 70 | 71 | if(StringUtils.hasLength(nickName)) { 72 | predicates.add(cb.like(root.get("nickName"),"%"+nickName+"%")); 73 | } 74 | 75 | if(StringUtils.hasLength(phone)) { 76 | predicates.add(cb.like(root.get("phone"),"%"+phone+"%")); 77 | } 78 | 79 | if(StringUtils.hasLength(email)) { 80 | predicates.add(cb.like(root.get("email"),"%"+email+"%")); 81 | } 82 | 83 | if(Objects.nonNull(state)) { 84 | predicates.add(cb.equal(root.get("state"), state)); 85 | } 86 | 87 | return query.where(cb.and(predicates.toArray(new Predicate[0]))).getRestriction(); 88 | }; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/entity/Book.java: -------------------------------------------------------------------------------- 1 | package com.cn.entity; 2 | 3 | import lombok.Data; 4 | import org.springframework.data.annotation.Id; 5 | import org.springframework.data.elasticsearch.annotations.Document; 6 | 7 | /** 8 | * @author ngcly 9 | */ 10 | @Data 11 | @Document(indexName = "book") 12 | public class Book { 13 | @Id 14 | private Long id; 15 | /**标题*/ 16 | private String title; 17 | /**作者*/ 18 | private String author; 19 | /**内容*/ 20 | private String content; 21 | } 22 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/entity/Carousel.java: -------------------------------------------------------------------------------- 1 | package com.cn.entity; 2 | 3 | import com.cn.config.AbstractUserDateAudit; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import jakarta.persistence.*; 8 | 9 | /** 10 | * 轮播图实体 11 | * 12 | * @author chen 13 | * @date 2017-12-30 17:41 14 | */ 15 | @Setter 16 | @Getter 17 | @Entity 18 | @Table(name = "carousel") 19 | public class Carousel extends AbstractUserDateAudit { 20 | 21 | @Id 22 | @GeneratedValue(strategy= GenerationType.IDENTITY) 23 | private Long id; 24 | 25 | /**图片地址*/ 26 | @Column(nullable = false) 27 | private String imageUrl; 28 | 29 | /**图片提示*/ 30 | @Column(length = 20) 31 | private String imageTip; 32 | 33 | /**跳转地址*/ 34 | @Column(nullable = false) 35 | private String forwardUrl; 36 | 37 | /**排序*/ 38 | private Integer sort; 39 | 40 | } 41 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/entity/CarouselCategory.java: -------------------------------------------------------------------------------- 1 | package com.cn.entity; 2 | 3 | import com.cn.config.AbstractUserDateAudit; 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import lombok.*; 6 | 7 | import jakarta.persistence.*; 8 | import java.util.List; 9 | 10 | /** 11 | * 轮播图分类实体 12 | * @author ngcly 13 | */ 14 | @Getter 15 | @Setter 16 | @Entity 17 | @Table(name = "carousel_category") 18 | @JsonIgnoreProperties(value = {"handler","hibernateLazyInitializer","fieldHandler"}) 19 | public class CarouselCategory extends AbstractUserDateAudit { 20 | 21 | @Id 22 | @GeneratedValue(strategy= GenerationType.IDENTITY) 23 | private Long id; 24 | 25 | /**标题名称*/ 26 | @Column(nullable = false,length = 10) 27 | private String title; 28 | 29 | /**描述*/ 30 | @Column(length = 32) 31 | private String remark; 32 | 33 | /**轮播图列表*/ 34 | @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) 35 | @JoinColumn(name = "categoryId") 36 | private List carousels; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/entity/Classify.java: -------------------------------------------------------------------------------- 1 | package com.cn.entity; 2 | 3 | import com.cn.config.AbstractUserDateAudit; 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import jakarta.persistence.*; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | /** 10 | * 分类表实体 11 | * 12 | * @author chen 13 | * @date 2017-12-30 17:37 14 | */ 15 | @Getter 16 | @Setter 17 | @Entity 18 | @Table(name = "classify") 19 | @JsonIgnoreProperties(value = {"handler","hibernateLazyInitializer","fieldHandler"}) 20 | public class Classify extends AbstractUserDateAudit { 21 | 22 | @Id 23 | @GeneratedValue(strategy= GenerationType.IDENTITY) 24 | private Long id; 25 | 26 | /**分类名称*/ 27 | @Column(nullable = false, length = 8) 28 | private String name; 29 | 30 | /**分类说明*/ 31 | @Column(length = 100) 32 | private String introduction; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/entity/Comment.java: -------------------------------------------------------------------------------- 1 | package com.cn.entity; 2 | 3 | import com.cn.config.AbstractDateAudit; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import jakarta.persistence.*; 8 | 9 | /** 10 | * 评论表实体 11 | * 12 | * @author chen 13 | * @date 2017-12-30 17:38 14 | */ 15 | @Setter 16 | @Getter 17 | @Entity 18 | @Table(name = "comment") 19 | public class Comment extends AbstractDateAudit { 20 | 21 | @Id 22 | @GeneratedValue(strategy=GenerationType.IDENTITY) 23 | private Long id; 24 | 25 | /**用户id*/ 26 | @Column(nullable = false) 27 | private Long userId; 28 | 29 | /**文章id*/ 30 | @Column(nullable = false) 31 | private Long essayId; 32 | 33 | /**内容*/ 34 | @Column(nullable = false) 35 | private String content; 36 | 37 | /**回复用户ID*/ 38 | private Long replyUserId; 39 | 40 | /**回复评论ID*/ 41 | private Long replyCommentId; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/entity/Daily.java: -------------------------------------------------------------------------------- 1 | package com.cn.entity; 2 | 3 | import com.cn.config.AbstractDateAudit; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import jakarta.persistence.*; 8 | 9 | /** 10 | * @author ngcly 11 | */ 12 | @Getter 13 | @Setter 14 | @Entity 15 | @Table(name = "daily") 16 | public class Daily extends AbstractDateAudit { 17 | @Id 18 | @GeneratedValue(strategy= GenerationType.IDENTITY) 19 | private Long id; 20 | 21 | @Column(nullable = false) 22 | private String content; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/entity/Essay.java: -------------------------------------------------------------------------------- 1 | package com.cn.entity; 2 | 3 | import com.cn.config.AbstractDateAudit; 4 | import com.cn.enums.EssayStatusEnum; 5 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | import jakarta.persistence.*; 10 | import java.util.List; 11 | 12 | /** 13 | * 文章表实体 14 | * @author ngcly 15 | * @since 2017-12-30 17:39 16 | */ 17 | @Setter 18 | @Getter 19 | @Entity 20 | @Table(name = "essay") 21 | @JsonIgnoreProperties(value = {"handler","hibernateLazyInitializer","fieldHandler"}) 22 | public class Essay extends AbstractDateAudit { 23 | @Id 24 | @GeneratedValue(strategy= GenerationType.IDENTITY) 25 | private Long id; 26 | 27 | /**文章标题*/ 28 | @Column(nullable = false,length = 80) 29 | private String title; 30 | 31 | /**简介*/ 32 | @Column(nullable = false) 33 | private String synopsis; 34 | 35 | /**文章内容*/ 36 | @Lob 37 | @Basic(fetch = FetchType.LAZY) 38 | // @Column(columnDefinition="mediumtext", nullable = false) 39 | private String content; 40 | 41 | /**阅览数*/ 42 | @Column(columnDefinition = "int default 0",nullable = false) 43 | private Integer readNum; 44 | 45 | /**状态*/ 46 | @Column(nullable = false) 47 | @Enumerated(EnumType.STRING) 48 | private EssayStatusEnum state; 49 | 50 | /**审核不通过原由*/ 51 | private String remark; 52 | 53 | /**文章所属用户*/ 54 | @ManyToOne(fetch = FetchType.LAZY) 55 | @JoinColumn(name = "userId",nullable = false) 56 | private User user; 57 | 58 | /**文章所属分类*/ 59 | @ManyToOne(fetch = FetchType.LAZY) 60 | @JoinColumn(name = "classifyId",nullable = false) 61 | private Classify classify; 62 | 63 | /**文章音乐列表*/ 64 | @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) 65 | @JoinColumn(name = "essayId", referencedColumnName = "id") 66 | private List musicList; 67 | 68 | } 69 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/entity/LoginLog.java: -------------------------------------------------------------------------------- 1 | package com.cn.entity; 2 | 3 | import com.cn.enums.LoginStatusEnum; 4 | import com.cn.enums.UserTypeEnum; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import org.springframework.format.annotation.DateTimeFormat; 8 | 9 | import jakarta.persistence.*; 10 | 11 | import java.time.Instant; 12 | import java.time.LocalDateTime; 13 | 14 | /** 15 | * 登录日志表实体 16 | * 17 | * @author chen 18 | * @date 2018-01-02 16:52 19 | */ 20 | @Getter 21 | @Setter 22 | @Entity 23 | @Table(name = "login_log") 24 | public class LoginLog { 25 | 26 | @Id 27 | @GeneratedValue(strategy= GenerationType.IDENTITY) 28 | private Long id; 29 | /**登录用户名*/ 30 | @Column(nullable = false, length = 32) 31 | private String userName; 32 | 33 | /**用户类型*/ 34 | @Column(nullable = false) 35 | @Enumerated(EnumType.STRING) 36 | private UserTypeEnum userType; 37 | 38 | /**登录IP*/ 39 | @Column(length = 20) 40 | private String loginIp; 41 | 42 | /**登录地址*/ 43 | @Column(length = 50) 44 | private String loginAddress; 45 | 46 | /**登录浏览器*/ 47 | @Column(length = 32) 48 | private String loginBrowser; 49 | 50 | /**登录时间*/ 51 | @Column(nullable = false) 52 | private Instant loginTime; 53 | 54 | /**登录状态*/ 55 | @Column(nullable = false) 56 | @Enumerated(EnumType.STRING) 57 | private LoginStatusEnum loginStatus; 58 | 59 | @Transient 60 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 61 | private Instant beginTime; 62 | @Transient 63 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 64 | private Instant endTime; 65 | 66 | } 67 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/entity/Music.java: -------------------------------------------------------------------------------- 1 | package com.cn.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import jakarta.persistence.*; 7 | import java.io.Serializable; 8 | 9 | /** 10 | * 音乐表实体 11 | * @author ngcly 12 | * @date 2017-12-30 17:40 13 | */ 14 | @Getter 15 | @Setter 16 | @Entity 17 | @Table(name = "music") 18 | public class Music implements Serializable { 19 | @Id 20 | @GeneratedValue(strategy= GenerationType.IDENTITY) 21 | private Long id; 22 | 23 | /**歌名*/ 24 | @Column(nullable = false, length = 32) 25 | private String name; 26 | 27 | /**歌手*/ 28 | @Column(length = 20) 29 | private String artist; 30 | 31 | /**专辑*/ 32 | @Column(length = 32) 33 | private String album; 34 | 35 | /**封面*/ 36 | private String cover; 37 | 38 | /**歌词*/ 39 | private String lrc; 40 | 41 | /**文章id*/ 42 | @Column(nullable = false) 43 | private Long essayId; 44 | 45 | /**链接地址*/ 46 | @Column(nullable = false) 47 | private String url; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/entity/News.java: -------------------------------------------------------------------------------- 1 | package com.cn.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import jakarta.persistence.*; 7 | 8 | import java.time.Instant; 9 | import java.time.LocalDateTime; 10 | 11 | /** 12 | * 消息表实体 13 | * 14 | * @author chen 15 | * @date 2017-12-30 17:42 16 | */ 17 | @Getter 18 | @Setter 19 | @Entity 20 | @Table(name = "news") 21 | public class News { 22 | 23 | @Id 24 | @GeneratedValue(strategy= GenerationType.IDENTITY) 25 | private Long id; 26 | 27 | /** 用户id */ 28 | @Column(nullable = false) 29 | private Long userId; 30 | 31 | /** 发送人id */ 32 | @Column(nullable = false) 33 | private Long senderId; 34 | 35 | /** 消息内容 */ 36 | @Column(nullable = false) 37 | private String content; 38 | 39 | /** 是否已发送 */ 40 | @Column(nullable = false) 41 | private Boolean sent; 42 | 43 | /** 创建时间 */ 44 | @Column(nullable = false) 45 | private Instant createTime; 46 | 47 | /** 发送时间 */ 48 | @Column(nullable = false) 49 | private Instant sendTime; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/entity/Notice.java: -------------------------------------------------------------------------------- 1 | package com.cn.entity; 2 | 3 | import com.cn.config.AbstractUserDateAudit; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import org.springframework.format.annotation.DateTimeFormat; 7 | 8 | import jakarta.persistence.*; 9 | 10 | import java.time.Instant; 11 | import java.time.LocalDateTime; 12 | 13 | /** 14 | * 公告表 15 | * @author ngcly 16 | * @since 2017-12-30 17:36 17 | */ 18 | @Getter 19 | @Setter 20 | @Entity 21 | @Table(name = "notice") 22 | public class Notice extends AbstractUserDateAudit { 23 | @Id 24 | @GeneratedValue(strategy= GenerationType.IDENTITY) 25 | private Long id; 26 | 27 | /**公告标题*/ 28 | @Column(nullable = false, length = 60) 29 | private String title; 30 | 31 | /**公告内容*/ 32 | @Column(nullable = false) 33 | private String content; 34 | 35 | /**公告类型 暂不用*/ 36 | @Column(length = 8) 37 | private String noticeType; 38 | 39 | /**展示开始时间*/ 40 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 41 | private Instant beginTime; 42 | 43 | /**展示结束时间*/ 44 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 45 | private Instant endTime; 46 | 47 | @Transient 48 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 49 | private Instant showTime; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/entity/Permission.java: -------------------------------------------------------------------------------- 1 | package com.cn.entity; 2 | 3 | import com.cn.enums.ResourceEnum; 4 | import com.cn.model.MenuDTO; 5 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 6 | import lombok.Setter; 7 | 8 | import jakarta.persistence.*; 9 | import java.util.Set; 10 | 11 | /** 12 | * 后台资源实体类 13 | * 14 | * @author ngcly 15 | */ 16 | @Setter 17 | @Entity 18 | @Table(name = "permission") 19 | @JsonIgnoreProperties(value = {"roles", "handler", "hibernateLazyInitializer", "fieldHandler"}) 20 | public class Permission extends MenuDTO { 21 | 22 | /** 23 | * 资源类型 24 | */ 25 | private ResourceEnum resourceType; 26 | 27 | /** 28 | * 请求方式 29 | */ 30 | private String httpMethod; 31 | 32 | /** 33 | * 父级id列表 34 | */ 35 | private String parentIds; 36 | 37 | /** 38 | * 排序号 39 | */ 40 | private Integer sort; 41 | 42 | /** 43 | * 菜单与角色 多对多 44 | */ 45 | private Set roles; 46 | 47 | @Override 48 | @Id 49 | @GeneratedValue(strategy = GenerationType.IDENTITY) 50 | public Long getId() { 51 | return id; 52 | } 53 | 54 | @Override 55 | @Column(nullable = false, length = 12) 56 | public String getName() { 57 | return name; 58 | } 59 | 60 | @Override 61 | @Column(length = 32) 62 | public String getUrl() { 63 | return url; 64 | } 65 | 66 | @Override 67 | @Column(length = 50) 68 | public String getIcon() { 69 | return icon; 70 | } 71 | 72 | @Override 73 | @Column(nullable = false) 74 | public Long getParentId() { 75 | return parentId; 76 | } 77 | 78 | @Enumerated(EnumType.STRING) 79 | @Column(nullable = false, length = 10) 80 | public ResourceEnum getResourceType() { 81 | return resourceType; 82 | } 83 | 84 | @Column(length = 32) 85 | public String getHttpMethod() { 86 | return httpMethod; 87 | } 88 | 89 | @Column(nullable = false, length = 100) 90 | public String getParentIds() { 91 | return parentIds; 92 | } 93 | 94 | public Integer getSort() { 95 | return sort; 96 | } 97 | 98 | @ManyToMany(mappedBy = "permissions") 99 | public Set getRoles() { 100 | return roles; 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/entity/Role.java: -------------------------------------------------------------------------------- 1 | package com.cn.entity; 2 | 3 | import com.cn.config.AbstractUserDateAudit; 4 | import com.cn.enums.UserTypeEnum; 5 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | import jakarta.persistence.*; 10 | import java.util.Objects; 11 | import java.util.Set; 12 | 13 | /** 14 | * 角色实体 15 | * 16 | * @author ngcly 17 | * @date 2017/6/23 18 | */ 19 | @Setter 20 | @Getter 21 | @Entity 22 | @Table(name = "role", uniqueConstraints = @UniqueConstraint(columnNames = {"roleCode", "roleType"})) 23 | @JsonIgnoreProperties(value = {"userInfoList", "managers", "handler", "hibernateLazyInitializer", "fieldHandler"}) 24 | public class Role extends AbstractUserDateAudit { 25 | 26 | @Id 27 | @GeneratedValue(strategy = GenerationType.IDENTITY) 28 | private Long id; 29 | 30 | /** 31 | * 角色名称 32 | */ 33 | @Column(nullable = false, length = 16) 34 | private String roleName; 35 | 36 | /** 37 | * 角色标识符 38 | */ 39 | @Column(nullable = false, length = 32) 40 | private String roleCode; 41 | 42 | /** 43 | * 角色类型 44 | */ 45 | @Column(nullable = false) 46 | @Enumerated(EnumType.STRING) 47 | private UserTypeEnum roleType; 48 | 49 | /** 50 | * 角色描述 51 | */ 52 | @Column(length = 50) 53 | private String description; 54 | /** 55 | * 是否可用,如果不可用将不会添加给用户 56 | */ 57 | 58 | @Column(nullable = false) 59 | private Boolean available; 60 | 61 | /** 62 | * 用户 - 角色关系定义; 用户-角色 多对多 63 | */ 64 | @ManyToMany(mappedBy = "roleList") 65 | private Set userInfoList; 66 | 67 | /** 68 | * 管理员 - 角色关系定义; 管理员-角色 多对多 69 | */ 70 | @ManyToMany(mappedBy = "roleList") 71 | private Set managers; 72 | 73 | /** 74 | * 角色 -- 权限关系:多对多关系; 75 | */ 76 | @ManyToMany(fetch = FetchType.LAZY) 77 | @JoinTable( 78 | name = "role_permission", 79 | joinColumns = @JoinColumn(name = "role_id"), 80 | inverseJoinColumns = @JoinColumn(name = "permission_id") 81 | ) 82 | @OrderBy("sort asc") 83 | private Set permissions; 84 | 85 | 86 | @Override 87 | public boolean equals(Object o) { 88 | if (this == o) { 89 | return true; 90 | } 91 | if (o == null || getClass() != o.getClass()) { 92 | return false; 93 | } 94 | Role role = (Role) o; 95 | return Objects.equals(id, role.id) && 96 | Objects.equals(roleName, role.roleName) && 97 | Objects.equals(roleType, role.roleType); 98 | } 99 | 100 | @Override 101 | public int hashCode() { 102 | return Objects.hash(id, roleName, roleType); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/entity/SocialInfo.java: -------------------------------------------------------------------------------- 1 | package com.cn.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import jakarta.persistence.*; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * 社会化信息实体 12 | * 13 | * @author ngcly 14 | * @version V1.0 15 | * @since 2021/8/23 11:20 16 | */ 17 | @Getter 18 | @Setter 19 | @Entity 20 | @Table(name = "social_info") 21 | public class SocialInfo implements Serializable { 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | private Long id; 25 | 26 | /** 27 | * 第三方系统的唯一ID 28 | */ 29 | @Column(nullable = false, length = 32) 30 | private String uuid; 31 | 32 | /** 33 | * 第三方用户来源 34 | */ 35 | @Column(nullable = false, length = 15) 36 | private String source; 37 | 38 | /** 39 | * 用户的授权令牌 40 | */ 41 | @Column(nullable = false, length = 50) 42 | private String accessToken; 43 | 44 | /** 45 | * 第三方用户的授权令牌的有效期 46 | */ 47 | private Integer expireIn; 48 | 49 | /** 50 | * 刷新令牌 51 | */ 52 | @Column(length = 50) 53 | private String refreshToken; 54 | 55 | /** 56 | * 第三方用户的 open id 57 | */ 58 | @Column(length = 50) 59 | private String openId; 60 | 61 | /** 62 | * 第三方用户的 ID 63 | */ 64 | @Column(length = 50) 65 | private String uid; 66 | 67 | /** 68 | * 第三方用户的 union id 69 | */ 70 | @Column(length = 50) 71 | private String unionId; 72 | 73 | /** 74 | * 第三方用户授予的权限 75 | */ 76 | @Column(length = 50) 77 | private String scope; 78 | 79 | @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.REMOVE) 80 | @JoinColumn(name = "userId") 81 | private User user; 82 | 83 | } 84 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/entity/UserFaves.java: -------------------------------------------------------------------------------- 1 | package com.cn.entity; 2 | 3 | import com.cn.config.AbstractDateAudit; 4 | import com.cn.enums.FaveTypeEnum; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import jakarta.persistence.*; 9 | 10 | /** 11 | * 用户点赞收藏表 12 | * @author chen 13 | * @date 2017-12-30 17:34 14 | */ 15 | @Setter 16 | @Getter 17 | @Entity 18 | @Table(name = "user_faves") 19 | public class UserFaves extends AbstractDateAudit { 20 | 21 | @Id 22 | @GeneratedValue(strategy=GenerationType.IDENTITY) 23 | private Long id; 24 | 25 | /**会员ID*/ 26 | @Column(nullable = false) 27 | private Long userId; 28 | 29 | /**文章ID*/ 30 | @Column(nullable = false) 31 | private Long essayId; 32 | 33 | /**类型*/ 34 | @Column(nullable = false) 35 | @Enumerated(EnumType.STRING) 36 | private FaveTypeEnum faveType; 37 | } 38 | -------------------------------------------------------------------------------- /repository/src/main/java/com/cn/entity/UserFollow.java: -------------------------------------------------------------------------------- 1 | package com.cn.entity; 2 | 3 | import com.cn.config.AbstractDateAudit; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import jakarta.persistence.*; 8 | 9 | /** 10 | * 用户关注关系表 11 | * @author ngcly 12 | * @since 2017-12-30 17:21 13 | */ 14 | @Getter 15 | @Setter 16 | @Entity 17 | @Table(name = "user_follow") 18 | public class UserFollow extends AbstractDateAudit { 19 | 20 | @Id 21 | @GeneratedValue(strategy=GenerationType.IDENTITY) 22 | private Long id; 23 | 24 | /**关注者ID*/ 25 | @Column(nullable = false) 26 | private Long userId; 27 | 28 | /**被关注的人ID*/ 29 | @Column(nullable = false) 30 | private Long followId; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /service/build.gradle: -------------------------------------------------------------------------------- 1 | jar { 2 | enabled = true 3 | } 4 | bootJar { 5 | enabled = false 6 | } 7 | 8 | dependencies { 9 | api project(":repository") 10 | } 11 | -------------------------------------------------------------------------------- /service/src/main/java/com/cn/BookService.java: -------------------------------------------------------------------------------- 1 | package com.cn; 2 | 3 | import com.cn.dao.BookRepository; 4 | import com.cn.entity.Book; 5 | import lombok.AllArgsConstructor; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.PageImpl; 8 | import org.springframework.data.domain.Pageable; 9 | import org.springframework.data.elasticsearch.core.*; 10 | import org.springframework.stereotype.Service; 11 | 12 | /** 13 | * @author ngcly 14 | * ES 文章 service 15 | */ 16 | @Service 17 | @AllArgsConstructor 18 | public class BookService { 19 | private final BookRepository bookRepository; 20 | 21 | /** 22 | * 高亮查询文章 23 | * 24 | * @param keyword 关键词 25 | * @param pageable 分页 26 | * @return Page> 27 | */ 28 | public Page> highLightSearchEssay(String keyword, Pageable pageable) { 29 | var list = bookRepository.findBooksByTitleOrContent(keyword, keyword, pageable); 30 | return new PageImpl<>(list); 31 | } 32 | 33 | /** 34 | * 保存到 es 35 | * 36 | * @param book 文章 37 | */ 38 | public void save(Book book) { 39 | bookRepository.save(book); 40 | } 41 | 42 | /** 43 | * 从ES删除文章 44 | * 45 | * @param book 文章 46 | */ 47 | public void delete(Book book) { 48 | bookRepository.delete(book); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /service/src/main/java/com/cn/CarouselService.java: -------------------------------------------------------------------------------- 1 | package com.cn; 2 | 3 | import com.cn.dao.CarouselRepository; 4 | import com.cn.entity.Carousel; 5 | import com.cn.entity.CarouselCategory; 6 | import com.cn.util.UploadUtil; 7 | import lombok.AllArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.data.domain.Page; 10 | import org.springframework.data.domain.Pageable; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.transaction.annotation.Transactional; 13 | 14 | /** 15 | * @author ngcly 16 | */ 17 | @Slf4j 18 | @Service 19 | @AllArgsConstructor 20 | public class CarouselService { 21 | private final CarouselRepository carouselRepository; 22 | 23 | /** 24 | * 根据条件获取轮播图列表 25 | */ 26 | public Page getCarouselList(String name, Pageable pageable) { 27 | return carouselRepository.findAll(CarouselRepository.getCarouselCategory(name), pageable); 28 | } 29 | 30 | /** 31 | * 获取轮播图详情 32 | * 33 | * @param id 主键 34 | */ 35 | public CarouselCategory getCarouselDetail(Long id) { 36 | return carouselRepository.findById(id).orElse(null); 37 | } 38 | 39 | /** 40 | * 新增修改轮播图 41 | * 42 | * @param carouselCategory 轮播图 43 | */ 44 | @Transactional(rollbackFor = Exception.class) 45 | public void addOrUpdateCarousel(CarouselCategory carouselCategory) { 46 | carouselRepository.save(carouselCategory); 47 | } 48 | 49 | /** 50 | * 添加轮播图 51 | * 52 | * @param id 分类ID 53 | * @param url 图片路径 54 | */ 55 | public void addCarousel(Long id, String url) { 56 | CarouselCategory carouselCategory = carouselRepository.getReferenceById(id); 57 | Carousel carousel = new Carousel(); 58 | carousel.setImageUrl(url); 59 | carousel.setSort(carouselCategory.getCarousels().size() + 1); 60 | carouselCategory.getCarousels().add(carousel); 61 | carouselRepository.save(carouselCategory); 62 | } 63 | 64 | /** 65 | * 删除轮播分类 66 | * 67 | * @param id 唯一标识 68 | */ 69 | @Transactional(rollbackFor = Exception.class) 70 | public void deleteCarouselCategory(Long id) { 71 | CarouselCategory carouselCategory = carouselRepository.getReferenceById(id); 72 | try { 73 | for (Carousel carousel : carouselCategory.getCarousels()) { 74 | UploadUtil.deleteFileByAli(carousel.getImageUrl()); 75 | } 76 | } catch (Exception e) { 77 | log.error("delete carousel category error:", e); 78 | } 79 | carouselRepository.delete(carouselCategory); 80 | } 81 | 82 | /** 83 | * 删除轮播图 84 | * 85 | * @param id 主键 86 | * @param url 图片地址 87 | */ 88 | @Transactional(rollbackFor = Exception.class) 89 | public void deleteCarousel(Long id, String url) { 90 | try { 91 | UploadUtil.deleteFileByAli(url); 92 | } catch (Exception e) { 93 | log.error("delete carousel error:", e); 94 | } 95 | carouselRepository.deleteCarousel(id); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /service/src/main/java/com/cn/ClassifyService.java: -------------------------------------------------------------------------------- 1 | package com.cn; 2 | 3 | import com.cn.dao.ClassifyRepository; 4 | import com.cn.entity.Classify; 5 | import lombok.AllArgsConstructor; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.Pageable; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * @author ngcly 15 | */ 16 | @Service 17 | @AllArgsConstructor 18 | public class ClassifyService { 19 | private final ClassifyRepository classifyRepository; 20 | 21 | /** 22 | * 根据条件获取分类列表 23 | * @param pageable 分页 24 | * @param classify 条件 25 | * @return Page 26 | */ 27 | public Page getClassifyList(Pageable pageable, Classify classify){ 28 | return classifyRepository.findAll(ClassifyRepository.getClassifyList(classify.getName()),pageable); 29 | } 30 | 31 | /** 32 | * 获取所有分类列表 33 | * @return List 34 | */ 35 | public List getClassifyList(){ 36 | return classifyRepository.findAll(); 37 | } 38 | 39 | /** 40 | * 获取分类详情 41 | * @param id 主键 42 | * @return Classify 43 | */ 44 | public Classify getClassifyDetail(Long id){ 45 | return classifyRepository.getReferenceById(id); 46 | } 47 | 48 | /** 49 | * 保存分类 50 | * @param classify 分类内容 51 | */ 52 | @Transactional(rollbackFor = Exception.class) 53 | public void saveClassify(Classify classify){ 54 | classifyRepository.save(classify); 55 | } 56 | 57 | /** 58 | * 根据id删除分类 59 | * @param id 主键 60 | */ 61 | @Transactional(rollbackFor = Exception.class) 62 | public void deleteClassify(Long id){ 63 | classifyRepository.deleteById(id); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /service/src/main/java/com/cn/LogService.java: -------------------------------------------------------------------------------- 1 | package com.cn; 2 | 3 | import cn.hutool.http.useragent.UserAgent; 4 | import cn.hutool.http.useragent.UserAgentUtil; 5 | import com.cn.dao.LoginLogRepository; 6 | import com.cn.entity.LoginLog; 7 | import com.cn.enums.LoginStatusEnum; 8 | import com.cn.enums.UserTypeEnum; 9 | import com.cn.model.AuthenticationDetails; 10 | import com.cn.util.IpUtil; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.context.event.EventListener; 13 | import org.springframework.data.domain.Page; 14 | import org.springframework.data.domain.Pageable; 15 | import org.springframework.scheduling.annotation.Async; 16 | import org.springframework.security.authentication.event.AbstractAuthenticationEvent; 17 | import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent; 18 | import org.springframework.security.authentication.event.AuthenticationSuccessEvent; 19 | import org.springframework.security.core.Authentication; 20 | import org.springframework.stereotype.Service; 21 | import org.springframework.transaction.annotation.Transactional; 22 | 23 | import java.time.Instant; 24 | import java.util.Objects; 25 | 26 | /** 27 | * 系统日志 service 28 | * 29 | * @author ngcly 30 | * @since 2018-03-22 16:24 31 | */ 32 | @Service 33 | @RequiredArgsConstructor 34 | public class LogService { 35 | private final LoginLogRepository loginLogRepository; 36 | 37 | /** 38 | * 获取登录日志列表 39 | */ 40 | public Page getLoginLogList(Pageable pageable, LoginLog loginLog) { 41 | return loginLogRepository.findAll(LoginLogRepository.getLoginLogList(loginLog.getUserName(), 42 | loginLog.getUserType(), loginLog.getBeginTime(), loginLog.getEndTime()), pageable); 43 | } 44 | 45 | @Async 46 | @Transactional(rollbackFor = Exception.class) 47 | @EventListener(value = {AbstractAuthenticationFailureEvent.class, AuthenticationSuccessEvent.class}) 48 | public void addLog(AbstractAuthenticationEvent event) { 49 | Authentication authentication = event.getAuthentication(); 50 | AuthenticationDetails details = (AuthenticationDetails) event.getAuthentication().getDetails(); 51 | UserTypeEnum userType = details.isAdministrator() ? UserTypeEnum.ADMIN : UserTypeEnum.USER; 52 | String ip = details.getRemoteAddress(); 53 | UserAgent userAgent = UserAgentUtil.parse(details.getUserAgent()); 54 | LoginStatusEnum loginStatus = event instanceof AuthenticationSuccessEvent ? 55 | LoginStatusEnum.SUCCESS : LoginStatusEnum.FAILURE; 56 | 57 | LoginLog loginLog = new LoginLog(); 58 | loginLog.setUserName(authentication.getName()); 59 | loginLog.setUserType(userType); 60 | loginLog.setLoginIp(ip); 61 | loginLog.setLoginAddress(IpUtil.getIpAddresses(ip)); 62 | loginLog.setLoginBrowser(Objects.nonNull(userAgent) ? userAgent.getBrowser().toString() : ""); 63 | loginLog.setLoginStatus(loginStatus); 64 | loginLog.setLoginTime(Instant.now()); 65 | saveLog(loginLog); 66 | } 67 | 68 | /** 69 | * 保存日志 70 | */ 71 | @Transactional(rollbackFor = Exception.class) 72 | public void saveLog(LoginLog loginLog) { 73 | loginLogRepository.save(loginLog); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /service/src/main/java/com/cn/NoticeService.java: -------------------------------------------------------------------------------- 1 | package com.cn; 2 | 3 | import com.cn.dao.NoticeRepository; 4 | import com.cn.entity.Notice; 5 | import lombok.AllArgsConstructor; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.Pageable; 8 | import org.springframework.data.domain.Sort; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | import java.time.LocalDateTime; 13 | import java.util.List; 14 | 15 | /** 16 | * @author ngcly 17 | */ 18 | @Service 19 | @AllArgsConstructor 20 | public class NoticeService { 21 | private final NoticeRepository noticeRepository; 22 | 23 | /** 24 | * 获取展示公告 25 | */ 26 | public List getNotice() { 27 | LocalDateTime now = LocalDateTime.now(); 28 | return noticeRepository.getNoticesByBeginTimeBeforeAndEndTimeAfterOrderByCreatedAtDesc(now, now); 29 | } 30 | 31 | /** 32 | * 获取历史公告 33 | * 34 | * @return List 35 | */ 36 | public List getNoticeList() { 37 | return noticeRepository.findAll(Sort.by("createdAt")); 38 | } 39 | 40 | /** 41 | * 获取公告详情 42 | * 43 | * @param id 主键 44 | * @return Notice 45 | */ 46 | public Notice getNoticeDetail(long id) { 47 | return noticeRepository.getReferenceById(id); 48 | } 49 | 50 | /** 51 | * 根据条件获取公告 52 | * 53 | * @param pageable 分页 54 | * @param notice 公告 55 | * @return Page 56 | */ 57 | public Page getNoticeList(Pageable pageable, Notice notice) { 58 | return noticeRepository.findAll(NoticeRepository.getNoticeList(notice.getTitle(), notice.getShowTime()), 59 | pageable); 60 | } 61 | 62 | /** 63 | * 新增修改公告 64 | * 65 | * @param notice 公告 66 | */ 67 | @Transactional(rollbackFor = Exception.class) 68 | public void addOrUpdateNotice(Notice notice) { 69 | noticeRepository.save(notice); 70 | } 71 | 72 | /** 73 | * 删除公告 74 | * 75 | * @param id 主键 76 | */ 77 | @Transactional(rollbackFor = Exception.class) 78 | public void deleteNotice(Long id) { 79 | noticeRepository.deleteById(id); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'MusicStory' 2 | include 'repository' 3 | include 'service' 4 | include 'interface' 5 | include 'backstage' 6 | include 'commons' 7 | 8 | --------------------------------------------------------------------------------