├── .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 | [](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 extends GrantedAuthority> 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 |
39 |
40 |
41 |
81 |
82 |
--------------------------------------------------------------------------------
/backstage/src/main/resources/templates/classify/classifyEdit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 新增修改分类页
6 |
7 |
8 |
9 |
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 |
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 |
23 | 初始化
24 | 正常
25 | 封禁
26 | |
27 |
28 |
29 | 生日: |
30 | |
31 | 头像: |
32 | ![]() |
33 |
34 |
35 | 创建时间: |
36 | |
37 | 修改时间: |
38 | |
39 |
40 |
41 | 角色: |
42 | |
43 |
44 |
45 |
46 |
47 |
48 |
58 |
59 |
--------------------------------------------------------------------------------
/backstage/src/main/resources/templates/manager/updatePwd.html:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 | 修改密码
7 |
8 |
9 |
10 |
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 |
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 |
29 | 禁用
30 | 正常
31 | 异常
32 | |
33 |
34 |
35 | 手机: |
36 | |
37 | 邮箱: |
38 | |
39 |
40 |
41 | 生日: |
42 | |
43 | 头像: |
44 | ![]() |
45 |
46 |
47 | 等级: |
48 | |
49 | 积分: |
50 | |
51 |
52 |
53 | 创建时间: |
54 | |
55 | 修改时间: |
56 | |
57 |
58 |
59 | 地址: |
60 | |
61 | 简介: |
62 | |
63 |
64 |
65 | 余额: |
66 | |
67 | 角色: |
68 | |
69 |
70 |
71 |
72 |
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