Compare commits

..

No commits in common. 'master' and 'main' have entirely different histories.
master ... main

8
.idea/.gitignore vendored

@ -1,8 +0,0 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="accountSettings">
<option name="activeRegion" value="us-east-1" />
<option name="recentlyUsedRegions">
<list>
<option value="us-east-1" />
</list>
</option>
</component>
</project>

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile default="true" name="Default" enabled="true" />
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="idempotent" />
<module name="sentinel" />
<module name="auth-service" />
<module name="cache" />
<module name="security" />
<module name="file" />
<module name="web" />
<module name="music-service" />
<module name="music-related-service" />
<module name="songlist-service" />
<module name="gateway" />
<module name="feign-api" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
<module name="eureka-server" target="11" />
<module name="redis-utils-spring-boot-starter" target="1.8" />
</bytecodeTargetLevel>
</component>
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
<module name="auth-service" options="-parameters" />
<module name="cache" options="-parameters" />
<module name="common" options="" />
<module name="eureka-server" options="-parameters" />
<module name="feign-api" options="-parameters" />
<module name="file" options="-parameters" />
<module name="gateway" options="-parameters" />
<module name="idempotent" options="-parameters" />
<module name="music-related-service" options="-parameters" />
<module name="music-service" options="-parameters" />
<module name="security" options="-parameters" />
<module name="sentinel" options="-parameters" />
<module name="songlist-service" options="-parameters" />
<module name="web" options="-parameters" />
</option>
</component>
</project>

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="cloudmusic@localhost" uuid="53a721df-c00d-4041-ad61-e0640ed15c76">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://localhost:3306/cloudmusic</jdbc-url>
<vm-options>-Djava.net.preferIPv4Stack=true</vm-options>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="postgres" uuid="6c8494e7-5d7f-4128-9f4e-2785d71a708f">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://8.210.250.29:5432/</jdbc-url>
<vm-options>-Djava.net.preferIPv4Stack=true</vm-options>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="@8.210.250.29" uuid="d49b167b-b85b-472a-ae80-068cc0b8961f">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://8.210.250.29:3306</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="@localhost" uuid="417deb03-8f32-42f9-bca8-0682793af9d9">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://localhost:3306</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/auth-service/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/auth-service/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/cache/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/cache/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/file/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/file/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/idempotent/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/idempotent/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/security/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/security/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/sentinel/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/sentinel/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/web/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/web/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/eureka-server/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/eureka-server/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/feign-api/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/feign-api/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/gateway/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/gateway/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/music-related-service/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/music-related-service/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/music-service/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/music-service/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/songlist-service/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/songlist-service/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
<option value="$PROJECT_DIR$/memorandum-auth/pom.xml" />
<option value="$PROJECT_DIR$/memorandum-event/pom.xml" />
<option value="$PROJECT_DIR$/memorandum-note/pom.xml" />
<option value="$PROJECT_DIR$/gateway/pom.xml" />
<option value="$PROJECT_DIR$/feign-api/pom.xml" />
<option value="$PROJECT_DIR$/common/web/pom.xml" />
<option value="$PROJECT_DIR$/common/oss/pom.xml" />
<option value="$PROJECT_DIR$/common/security/pom.xml" />
<option value="$PROJECT_DIR$/common/sentinel/pom.xml" />
<option value="$PROJECT_DIR$/common/file/pom.xml" />
<option value="$PROJECT_DIR$/common/cache/pom.xml" />
<option value="$PROJECT_DIR$/common/idempotent/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="TemplatesSettings">
<option name="templateConfigs">
<TemplateContext>
<option name="generateConfig">
<GenerateConfig>
<option name="annotationType" value="NONE" />
<option name="basePackage" value="generator" />
<option name="basePath" value="src/main/java/com/flyingpig/cloudmusic/music" />
<option name="classNameStrategy" value="camel" />
<option name="encoding" value="UTF-8" />
<option name="extraClassSuffix" value="" />
<option name="ignoreFieldPrefix" value="" />
<option name="ignoreFieldSuffix" value="" />
<option name="ignoreTablePrefix" value="" />
<option name="ignoreTableSuffix" value="" />
<option name="moduleName" value="cloudmusic-music" />
<option name="modulePath" value="$PROJECT_DIR$/cloudmusic-music" />
<option name="moduleUIInfoList">
<list>
<ModuleInfoGo>
<option name="basePath" value="src/main/java/com/flyingpig/cloudmusic/music" />
<option name="configFileName" value="domain.ftl" />
<option name="configName" value="domain" />
<option name="encoding" value="UTF-8" />
<option name="fileName" value="${domain.fileName}" />
<option name="fileNameWithSuffix" value="${domain.fileName}.java" />
<option name="modulePath" value="$PROJECT_DIR$/cloudmusic-music" />
<option name="packageName" value="generator.dataobject" />
</ModuleInfoGo>
</list>
</option>
<option name="needToStringHashcodeEquals" value="true" />
<option name="needsComment" value="true" />
<option name="needsModel" value="true" />
<option name="relativePackage" value="dataobject" />
<option name="superClass" value="" />
<option name="tableUIInfoList">
<list>
<TableUIInfo>
<option name="className" value="Like" />
<option name="tableName" value="like" />
</TableUIInfo>
</list>
</option>
<option name="templatesName" value="custom-model-swagger" />
</GenerateConfig>
</option>
<option name="moduleName" value="cloudmusic-music" />
<option name="projectPath" value="$PROJECT_DIR$" />
<option name="templateName" value="custom-model-swagger" />
</TemplateContext>
</option>
</component>
</project>

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SonarLintProjectSettings">
<option name="moduleMapping">
<map>
<entry key="file" value="oss" />
</map>
</option>
</component>
</project>

@ -1,124 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

@ -1,81 +1,2 @@
# 喵听
微服务架构的共享音乐APP
## 友链
前端项目:https://github.com/wuliwudu/MusicApp
UI:https://www.figma.com/file/Y4OJRtIMkpFlonuqF8EFpX/%E5%96%B5%E5%90%AC%EF%BC%88%E5%8F%91%E7%96%AF%E7%8B%82%E6%94%B9%E7%89%88%EF%BC%89?type=design&node-id=223-972&mode=design&t=KDQWV4e8yuRQAxTG-0
接口文档:https://apifox.com/apidoc/shared-d7e55ccc-d4b0-46f4-8092-a027767ed284
## 技术栈
后端采用SpirngBoot+Spring Cloud Alibaba+Redis+Rabbitmq+ElasticSearch进行开发。采用Docker进行部署。
微服务方面选取的具体组件如下:
| 功能 | 采用的组件 |
| ------------ | -------------------- |
| 服务调用 | OpenFeign |
| 服务注册发现 | Nacos |
| 网关 | Spring Cloud Gateway |
| 微服务保护 | Sentinel |
## 整体架构
![202401031857007](https://github.com/flying-pig-z/CloudMusic/assets/117554874/e2246ba1-6ebf-46ca-a7f0-a572a8c49744)
## 功能亮点
### 安全方面
实现了分布式认证和鉴权,具体架构如下:
![image](https://github.com/flying-pig-z/CloudMusic/assets/117554874/b91c8159-75c8-465c-a681-b58eb4e3fbae)
使用redis维护token键为用户id值为token对应的uuid限制单账号只能一个用户。
网关主要认证校验逻辑:白名单放行->检查token[是否为空or非法]->token是否在redis白名单中->放行并将用户id和权限信息传递给各个微服务。
在各个微服务中将网关传递的用户id和权限信息存入TreadLocal中。
各个微服务授权结合存储的权限信息和aop熟悉实现授权
### 优化性能方面
1.排行榜功能使用XXX-JOB定时任务实现并且采用了redis进行优化。
![image-20240509125127647](C:/Users/86138/AppData/Roaming/Typora/typora-user-images/image-20240509125127647.png)
2.业务逻辑严密,比如对上传文件的格式进行检查,避免重复的点赞和收藏。
3.redis优化用户信息和音乐信息查询
![image](https://github.com/flying-pig-z/CloudMusic/assets/117554874/3ba2601e-b6ea-45b7-a473-467fc1e4b6ca)
4.redis优化点赞
![whiteboard_exported_image (2)](https://github.com/flying-pig-z/CloudMusic/assets/117554874/66a57721-56c0-45a7-910d-8163d2d63595)
> 设计思路:<br>
> 【1】最终一致性一般一致性采用的是Cache Aside Pattern先更新数据库再删除缓存但是的话获取某个音乐的点赞集合到redis中是个耗时的操作。在加上更新的频繁所以不能采用删除缓存。<br>
> 那要不就先更新数据库再更新缓存,要不就先更新缓存再更新数据库。<br>
> 这里采用Write Behind Pattern。先更新缓存再异步更新数据库。优点是效率很高数据库压力很小.<br>
> 缺点是异步增大了数据库和缓存无法强一致的概率。比如说当过期的时候去读取,可能使得同一时间点赞或者取消点赞的数据更改并没有同步到缓存。但是的话可以结合前端缓存优化,问题不大。<br>
> 【2】缓存穿透防止数据库中的数据不存在导致这部分请求一直落在数据库。<br>
> 一般的解决方法有两种一种是缓存空值但是redis的复杂类型不能为空另外一种是布隆过滤器但是布隆过滤器会产生冲突。<br>
> 所以我在redis中也设计了一个变量作为计数器存储点赞总数来判断缓存不存在是数据过期还是数据库不存在。同时点赞总数也是热点查询数据。<br>
> 【3】竞态问题使用redission加锁解决竞态造成点赞数更新错误。<br>
> 场景例如两个用户同时查看该音乐的点赞数为10并都想取消点赞但是由于并发操作最终点赞数可能只减少了1次而不是2次。<br>
5.mq进行异步解耦优化上传接口
![whiteboard_exported_image (1)](https://github.com/flying-pig-z/CloudMusic/assets/117554874/5a0fd272-ba9c-45c5-96de-2dcfab4f4d66)
这么设计主要是因为前端在用户上传后返回给客户端审核后自动发布,上传文件的时效性并不高。再加上上传接口是个耗时的操作所以采用异步进行解耦。
6.使用ElasticSearch优化搜索
## 代办
实现大文件分片上传
# CloudMusic

@ -1,33 +0,0 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

Binary file not shown.

@ -1,2 +0,0 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar

308
auth-service/mvnw vendored

@ -1,308 +0,0 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.2.0
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "$(uname)" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
else
JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=$(java-config --jre-home)
fi
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="$(which javac)"
if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=$(which readlink)
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
if $darwin ; then
javaHome="$(dirname "\"$javaExecutable\"")"
javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
else
javaExecutable="$(readlink -f "\"$javaExecutable\"")"
fi
javaHome="$(dirname "\"$javaExecutable\"")"
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=$(cd "$wdir/.." || exit 1; pwd)
fi
# end of workaround
done
printf '%s' "$(cd "$basedir" || exit 1; pwd)"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
# Remove \r in case we run on Windows within Git Bash
# and check out the repository with auto CRLF management
# enabled. Otherwise, we may read lines that are delimited with
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
# splitting rules.
tr -s '\r\n' ' ' < "$1"
fi
}
log() {
if [ "$MVNW_VERBOSE" = true ]; then
printf '%s\n' "$1"
fi
}
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
log "$MAVEN_PROJECTBASEDIR"
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
if [ -r "$wrapperJarPath" ]; then
log "Found $wrapperJarPath"
else
log "Couldn't find $wrapperJarPath, downloading it ..."
if [ -n "$MVNW_REPOURL" ]; then
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
else
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
fi
while IFS="=" read -r key value; do
# Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
safeValue=$(echo "$value" | tr -d '\r')
case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
esac
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
log "Downloading from: $wrapperUrl"
if $cygwin; then
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
fi
if command -v wget > /dev/null; then
log "Found wget ... using wget"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
log "Found curl ... using curl"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
else
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
fi
else
log "Falling back to using Java to download"
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaSource=$(cygpath --path --windows "$javaSource")
javaClass=$(cygpath --path --windows "$javaClass")
fi
if [ -e "$javaSource" ]; then
if [ ! -e "$javaClass" ]; then
log " - Compiling MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/javac" "$javaSource")
fi
if [ -e "$javaClass" ]; then
log " - Running MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
wrapperSha256Sum=""
while IFS="=" read -r key value; do
case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
esac
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
if [ -n "$wrapperSha256Sum" ]; then
wrapperSha256Result=false
if command -v sha256sum > /dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
wrapperSha256Result=true
fi
elif command -v shasum > /dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
wrapperSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
exit 1
fi
if [ $wrapperSha256Result = false ]; then
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
exit 1
fi
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
# shellcheck disable=SC2086 # safe args
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

@ -1,205 +0,0 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.2.0
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %WRAPPER_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
SET WRAPPER_SHA_256_SUM=""
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
)
IF NOT %WRAPPER_SHA_256_SUM%=="" (
powershell -Command "&{"^
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
" Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
" Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
" Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
" exit 1;"^
"}"^
"}"
if ERRORLEVEL 1 goto error
)
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%

@ -1,120 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>cloud-music</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>auth-service</artifactId>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- springboot web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 持久层: mysql mybatis mybatis-plus -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-autoconfigure</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!-- springsecurity -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<!-- fast json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- mail -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- feign调用 -->
<dependency>
<groupId>com.flyingpig</groupId>
<artifactId>feign-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 服务通用模块 -->
<dependency>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common-web</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common-security</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common-file</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common-cache</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -1,17 +0,0 @@
package com.flyingpig.cloudmusic.auth;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@EnableSwagger2
@SpringBootApplication
@EnableAsync
public class AuthApplication {
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}

@ -1,35 +0,0 @@
package com.flyingpig.cloudmusic.auth.config;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
//默认的Key序列化器为JdkSerializationRedisSerializer,
// 使得在代码里key写的是什么存进redis里的就是什么不做变化
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);//其实value的序列化器可以不用更改存进去什么取出来还会反序列化一次
// Hash的key和value也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}

@ -1,29 +0,0 @@
package com.flyingpig.cloudmusic.auth.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host:localhost}")
private String redisHost;
@Value("${spring.redis.port:6379}")
private int redisPort;
@Bean
public RedissonClient redissonClient() {
// 配置
Config config = new Config();
config.useSingleServer().setAddress("redis://" + redisHost + ":" + redisPort);
// 创建RedissonClient对象
return Redisson.create(config);
}
}

@ -1,38 +0,0 @@
package com.flyingpig.cloudmusic.auth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不会创建HttpSession并且不通过Session获取SecurityContext对象,因为前后端分离基本session就已经灭有用了
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()//对请求认证规则进行相应配置
//对于登录,修改密码和注册接口放行(antMatchers)
.antMatchers("/**").permitAll()
.anyRequest().authenticated();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}

@ -1,16 +0,0 @@
package com.flyingpig.cloudmusic.auth.constant;
public class RedisConstants {
public static final String USER_INFO_KEY="user:info:";
public static final Long USER_INFO_TTL=30L;
public static final String USER_LOGIN_KEY="user:login:";
public static final Long USER_LOGIN_TTL = 30L;
public static final String EMAIL_VERIFYCODE_KEY="email:verifycode:";
public static final Long EMAIL_VERIFYCODE_TTL=120L;
}

@ -1,55 +0,0 @@
package com.flyingpig.cloudmusic.auth.controller;
import com.flyingpig.cloudmusic.auth.constant.RedisConstants;
import com.flyingpig.cloudmusic.auth.dataobject.entity.User;
import com.flyingpig.cloudmusic.security.util.JwtUtil;
import com.flyingpig.cloudmusic.auth.service.UserService;
import com.flyingpig.cloudmusic.web.Result;
import com.flyingpig.cloudmusic.web.StatusCode;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;
import java.util.Objects;
@RestController
@RequestMapping("/users")
@Api("用户操作相关的api")
@Slf4j
public class AuthController {
@Autowired
private UserService userService;
@Autowired
StringRedisTemplate stringRedisTemplate;
@PostMapping("/login")
@ApiOperation("用户登录")
public Result login(@RequestBody User user) {
log.info("用户登录:{}", user);
return userService.login(user);
}
@PostMapping("/logout")
@ApiOperation("用户登出")
public Result logout(@RequestHeader String Authorization) {
return userService.logout(JwtUtil.parseJwt(Authorization).getSubject());
}
@GetMapping("/whitelist")
public boolean uuidIsInWhiteListOrNot(String userId, String uuid) {
String redisValue = stringRedisTemplate.opsForValue().get(RedisConstants.USER_LOGIN_KEY + userId);
// 去掉引号和空格后再进行比较
if (redisValue != null) {
redisValue = redisValue.replace("\"", "").trim();
}
return Objects.equals(redisValue, uuid);
}
}

@ -1,61 +0,0 @@
package com.flyingpig.cloudmusic.auth.controller;
import com.flyingpig.cloudmusic.auth.constant.RedisConstants;
import com.flyingpig.cloudmusic.auth.dataobject.entity.User;
import com.flyingpig.cloudmusic.auth.dataobject.vo.EmailRegisterVO;
import com.flyingpig.cloudmusic.auth.service.UserService;
import com.flyingpig.cloudmusic.auth.util.EmailUtil;
import com.flyingpig.cloudmusic.web.Result;
import com.flyingpig.cloudmusic.web.StatusCode;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/email")
@Api("与邮件处理相关的api")
@Slf4j
public class MailController {
@Autowired
UserService userService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
PasswordEncoder passwordEncoder;
@GetMapping("/verificationCode")
@ApiOperation("用户获取验证码")
public Result sendEmailVerificationCode(String email) {
//检查email是否符合格式
if (!EmailUtil.judgeEmailFormat(email)) {
throw new RuntimeException("邮箱不符合格式");
}
userService.sendVerificationCode(email);
return Result.success("验证码已发送");
}
@PostMapping("/register")
@ApiOperation("通过验证码完成注册")
public Result emailRegister(@RequestBody EmailRegisterVO emailRegisterVO) {
String verificationCode = stringRedisTemplate.opsForValue().get(RedisConstants.EMAIL_VERIFYCODE_KEY + emailRegisterVO.getEmail());
log.info("邮箱{}请求验证码{}",emailRegisterVO.getEmail(),verificationCode);
if (verificationCode != null && verificationCode.equals(emailRegisterVO.getVerificationCode())) {
// 添加用户
userService.addUser(new User().setRole("user")
.setUsername(emailRegisterVO.getUsername())
.setPassword(passwordEncoder.encode(emailRegisterVO.getPassword()))
.setEmail(emailRegisterVO.getEmail()));
return Result.success("添加成功,请联系管理员审核");
} else {
throw new RuntimeException("验证码验证错误");
}
}
}

@ -1,62 +0,0 @@
package com.flyingpig.cloudmusic.auth.controller;
import com.flyingpig.cloudmusic.auth.service.UserService;
import com.flyingpig.cloudmusic.security.aop.BeforeAuthorize;
import com.flyingpig.cloudmusic.security.util.UserContext;
import com.flyingpig.cloudmusic.web.Result;
import com.flyingpig.feign.dataobject.dto.UserInfo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@RestController
@RequestMapping("/users")
@Api("用户操作相关的api")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/info")
@ApiOperation("获取用户信息")
public Result selectUserInfoByUserId() {
//封装完毕后调用service层的add方法
return Result.success(userService.selectUserInfoByUserId(UserContext.getUser().getUserId()));
}
@PutMapping("/avatar")
@ApiOperation("修改用户头像")
public Result updateUserAvatar(@RequestParam MultipartFile avatar) throws IOException {
//封装完毕后调用service层的add方法
userService.updateAvatar(UserContext.getUser().getUserId(), avatar);
return Result.success();
}
@PutMapping("/username")
@ApiOperation("修改用户名")
public Result updateUserName(String userName) {
userService.updateUserName(UserContext.getUser().getUserId(), userName);
return Result.success();
}
@ApiOperation("内部调用查询用户信息")
@GetMapping("/user-info/{userId}")
UserInfo selectUserInfoByUserId(@PathVariable("userId") Long userId) {
return userService.selectUserInfoByUserId(userId);
}
@DeleteMapping
@BeforeAuthorize(role = "admin")
@ApiOperation("管理员删除用户")
public Result deleteUser(Long userId) {
userService.deleteUserById(userId);
return Result.success();
}
}

@ -1,57 +0,0 @@
package com.flyingpig.cloudmusic.auth.dataobject.dto;
import com.flyingpig.cloudmusic.auth.dataobject.entity.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
private User user;
//获取用户权限
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
//判断用户名和密码是否没过期
@Override
public boolean isAccountNonExpired() {
return true;
}
//返回用户名
@Override
public String getUsername() {
return user.getEmail();
}
//返回密码
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

@ -1,25 +0,0 @@
package com.flyingpig.cloudmusic.auth.dataobject.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
@Accessors(chain = true)
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String username;
private String password;
private String email;
private String avatar;
private String role;
}

@ -1,18 +0,0 @@
package com.flyingpig.cloudmusic.auth.dataobject.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmailRegisterVO {
public String email;
public String verificationCode;
//用户名
private String username;
//密码
public String password;
}

@ -1,10 +0,0 @@
package com.flyingpig.cloudmusic.auth.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.flyingpig.cloudmusic.auth.dataobject.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

@ -1,26 +0,0 @@
package com.flyingpig.cloudmusic.auth.service;
import com.flyingpig.cloudmusic.auth.dataobject.entity.User;
import com.flyingpig.cloudmusic.web.Result;
import com.flyingpig.feign.dataobject.dto.UserInfo;
import org.springframework.web.multipart.MultipartFile;
public interface UserService {
Result login(User user);
Result logout(String userId);
void addUser(User user);
UserInfo selectUserInfoByUserId(Long userId);
void updateUserName(Long userId, String userName);
void updateAvatar(Long userId, MultipartFile avatarUrl);
void deleteUserById(Long userId);
void sendVerificationCode(String email);
}

@ -1,34 +0,0 @@
package com.flyingpig.cloudmusic.auth.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.flyingpig.cloudmusic.auth.dataobject.entity.User;
import com.flyingpig.cloudmusic.auth.mapper.UserMapper;
import com.flyingpig.cloudmusic.auth.dataobject.dto.LoginUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Objects;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
//查询用户信息
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("email", email);
User user = userMapper.selectOne(queryWrapper);
//如果没有查询到用户就抛出异常
if (Objects.isNull(user)) {
throw new RuntimeException("用户名或者密码错误");
}
//把数据封装成UserDetails返回
return new LoginUser(user);
}
}

@ -1,168 +0,0 @@
package com.flyingpig.cloudmusic.auth.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.flyingpig.cloudmusic.auth.constant.RedisConstants;
import com.flyingpig.cloudmusic.auth.dataobject.entity.User;
import com.flyingpig.cloudmusic.auth.mapper.UserMapper;
import com.flyingpig.cloudmusic.auth.service.UserService;
import com.flyingpig.cloudmusic.auth.util.EmailUtil;
import com.flyingpig.cloudmusic.cache.StringCacheUtil;
import com.flyingpig.cloudmusic.auth.dataobject.dto.LoginUser;
import com.flyingpig.cloudmusic.file.AliOSSUtils;
import com.flyingpig.cloudmusic.security.util.JwtUtil;
import com.flyingpig.cloudmusic.web.Result;
import com.flyingpig.feign.dataobject.dto.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class UserServiceImpl implements UserService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
StringCacheUtil myStringRedisTemplate;
@Autowired
private UserMapper userMapper;
@Autowired
AliOSSUtils aliOSSUtils;
@Value("${spring.mail.username}")
private String emailUserName;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Resource
private JavaMailSenderImpl mailSender;
@Override
public Result login(User user) {
//AuthenticationManager authenticate进行用户认证
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//如果认证没通过,给出对应的提示
if (Objects.isNull(authenticate)) {
throw new RuntimeException("账号或密码错误,请重新登录");
}
//如果认证通过了使用userid生成一个jwt jwt存入ResponseResult返回
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userid = loginUser.getUser().getId().toString();
String uuid = JwtUtil.getUUID();
String jwt = JwtUtil.createJWT(userid, JwtUtil.JWT_TTL, uuid);
//将jwt存入redis中键为userId,值为token的uuid不直接存jwt可以节省缓存
myStringRedisTemplate.set(RedisConstants.USER_LOGIN_KEY + userid, uuid, RedisConstants.USER_LOGIN_TTL, TimeUnit.DAYS);
//返回
Map<String, Object> map = new HashMap<>();
map.put("token", jwt);
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("email", user.getEmail());
User selectuser = userMapper.selectOne(userQueryWrapper);
map.put("email", selectuser.getEmail());
map.put("id", selectuser.getId());
return Result.success(map);
}
@Override
public Result logout(String userId) {
//将token加入黑名单
myStringRedisTemplate.delete(RedisConstants.USER_LOGIN_KEY + userId);
return new Result(200, "退出成功", null);
}
@Override
public void addUser(User user) {
userMapper.insert(user);
}
@Override
public UserInfo selectUserInfoByUserId(Long userId) {
return myStringRedisTemplate.safeGetWithLock(RedisConstants.USER_INFO_KEY + userId, UserInfo.class, () -> {
UserInfo result = new UserInfo();
User user = userMapper.selectById(userId);
if (user == null) {
return null;
}
BeanUtils.copyProperties(user, result);
return result;
}, RedisConstants.USER_INFO_TTL, TimeUnit.DAYS);
}
@Override
public void updateUserName(Long userId, String userName) {
User user = new User();
user.setId(userId);
user.setUsername(userName);
userMapper.updateById(user);
//删除缓存,防止数据不一致
myStringRedisTemplate.delete(RedisConstants.USER_INFO_KEY + userId);
}
@Override
public void updateAvatar(Long userId, MultipartFile avatarUrl) {
try {
// 删除原来的头像
aliOSSUtils.deleteFileByUrl(userMapper.selectById(userId).getAvatar());
// 更新现有的头像
userMapper.updateById(new User().setId(userId)
.setAvatar(aliOSSUtils.upload(avatarUrl)));
// 删除缓存,防止数据不一致
myStringRedisTemplate.delete(RedisConstants.USER_INFO_KEY + userId);
} catch (Exception e) {
throw new RuntimeException("修改头像异常" + e.getMessage());
}
}
@Override
public void deleteUserById(Long userId) {
userMapper.deleteById(userId);
}
@Override
@Async
public void sendVerificationCode(String email) {
// 验证码 邮件主题 邮件正文
String verificationCode = EmailUtil.createVerificationCode();
String subject = "【喵听】验证码";
String text = String.format("【喵听】验证码:%s您正在申请注册喵听账号" +
"(若非本人操作,请删除本邮件)", verificationCode);
// 发送邮件
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(emailUserName); // 设置发件邮箱
message.setTo(email); // 设置收件邮箱
message.setSubject(subject); // 设置邮件主题
message.setText(text); // 设置邮件正文
mailSender.send(message);
// 存入缓存
stringRedisTemplate.opsForValue().set(RedisConstants.EMAIL_VERIFYCODE_KEY + email, verificationCode, RedisConstants.EMAIL_VERIFYCODE_TTL, TimeUnit.SECONDS);
}
}

@ -1,32 +0,0 @@
package com.flyingpig.cloudmusic.auth.util;
import java.util.Random;
public class EmailUtil {
//判断邮箱格式
public static boolean judgeEmailFormat(String email) {
String regex = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
if (email.matches(regex)) {
return true;
} else {
return false;
}
}
//生成验证码
public static String createVerificationCode() {
String result = new String();
Random random = new Random();
for (int i = 0; i < 6; i++) {
int randomNumber = random.nextInt(10); // 生成0到9之间的随机整数
result = result + randomNumber;
}
return result;
}
//测试
public static void main(String[] args) {
String result = createVerificationCode();
System.out.println(judgeEmailFormat("1839976096@qq.com"));
}
}

@ -1,26 +0,0 @@
package com.flyingpig.cloudmusic.auth.util;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class WebUtils {
/**
*
*
* @param response
* @param string
* @return null
*/
public static String renderString(HttpServletResponse response, String string) {
try {
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

@ -1,65 +0,0 @@
server:
port: 9094
spring:
main:
allow-bean-definition-overriding: true
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
application:
name: auth-service # 服务名称
datasource:
url: jdbc:mysql://localhost:3306/cloudmusic
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: '@Aa123456'
cloud:
nacos:
server-addr: http://common-nacos-dev.magestack.cn:8848
discovery:
cluster-name: FJ # 集群名称
ip: 8.210.250.29 # 注册到nacos的ip与端口
port: 9094
redis:
host: localhost
port: 6379
database: 0
mail:
host: smtp.qq.com
username: flying_pig_z@qq.com
password:
default-encoding: UTF-8
port: 465
properties:
mail:
smtp:
socketFactory:
class: javax.net.ssl.SSLSocketFactory
ssl:
enable: true
#??sql
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mybatis:
configuration:
map-underscore-to-camel-case: true
#swagger配置
swagger:
title: "鉴权模块"
description: "鉴权模块接口"
base-package: com.flyingpig.cloudmusic.auth
enabled: true
version: 1.0.0
#feign远程调用配置
feign:
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
sentinel:
enabled: true

@ -1,10 +0,0 @@
spring:
application:
name: auth-service # 服务名称
profiles:
active: prod #运行环境
cloud:
nacos:
server-addr: http://47.122.56.65:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名

@ -1,12 +0,0 @@
package com.flyingpig.cloudmusic.auth;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class Test {
public static void main(String[] args){
BCryptPasswordEncoder bCryptPasswordEncoder=new BCryptPasswordEncoder();
String encodeString=bCryptPasswordEncoder.encode("1");
System.out.println(encodeString);
}
}

@ -1,33 +0,0 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>common-cache</artifactId>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.53</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.5.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version> <!-- 版本号可根据需要调整 -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.21.3</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

@ -1,14 +0,0 @@
package com.flyingpig.cloudmusic.cache;
/**
*
*
*/
@FunctionalInterface
public interface CacheLoader<T> {
/**
*
*/
T load();
}

@ -1,99 +0,0 @@
package com.flyingpig.cloudmusic.cache;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.nio.channels.ReadableByteChannel;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReadWriteLock;
public class StringCacheUtil {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedissonClient redissonClient;
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
// 加入缓存
public void set(String key, Object value, Long time, TimeUnit unit) {
stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(value), time, unit);
}
// 普通查询
public <T> T get(String key, Class<T> type) {
String value = stringRedisTemplate.opsForValue().get(key);
if (String.class.isAssignableFrom(type)) {
return (T) value;
}
return JSON.parseObject(value, type);
}
// 查询时候缓存空值防止缓存穿透,互斥锁查询防止缓存击穿
public <T> T safeGetWithLock(
String key, Class<T> type, CacheLoader<T> cacheLoader, Long time, TimeUnit unit) {
// 从redis查询缓存
String json = stringRedisTemplate.opsForValue().get(key);
// 1.命中且不为空字符串,直接返回;命中却为空字符串返回null
if (StrUtil.isNotBlank(json)) {
System.out.println(666);
return JSON.parseObject(json, type);
} else if (json != null) {
return null;
}
// 2.没有命中,去数据库查询,查到写入数据库,没查到则缓存空字符串
// 获取锁
String lockKey = "lock:" + key;
T result = null;
RLock rLock = redissonClient.getLock(lockKey);
rLock.lock();
try {
// 再次查询redis双重判定
if (StrUtil.isNotBlank(json)) {
return JSON.parseObject(json, type);
} else if (json != null) {
return null;
}
// 获取锁成功查询数据库。存在写入redis;不存在将空值写入redis返回null。
result = loadAndSet(key, cacheLoader, time, unit);
} finally {
// 释放锁
rLock.unlock();
}
// 返回
return result;
}
public void delete(String key) {
stringRedisTemplate.delete(key);
}
private <T> T loadAndSet(String key, CacheLoader<T> cacheLoader, Long time, TimeUnit unit) {
// 获取锁成功,查询数据库
T result = cacheLoader.load();
// 不存在将空值写入redis返回null
if (result == null) {
stringRedisTemplate.opsForValue().set(key, "", time, TimeUnit.MINUTES);
}
// 存在写入redis
this.set(key, result, time, unit);
return result;
}
}

@ -1,13 +0,0 @@
package com.flyingpig.cloudmusic.cache.config;
import com.flyingpig.cloudmusic.cache.StringCacheUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CacheAutoConfiguration {
@Bean
public StringCacheUtil stringCacheUtil() {
return new StringCacheUtil();
}
}

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.flyingpig.cloudmusic.cache.config.CacheAutoConfiguration

@ -1,33 +0,0 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>common-file</artifactId>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

@ -1,179 +0,0 @@
package com.flyingpig.cloudmusic.file;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;
public class AliOSSUtils {
private static String endpoint = "";
private static String bucketName = "";
private static String accessKeyId = "";
private static String accessKeySecret = "";
/**
* OSS
*/
public String upload(MultipartFile multipartFile) throws IOException {
// 获取上传的文件的输入流
InputStream inputStream = multipartFile.getInputStream();
// 生成新的文件名
String fileName = generateFileName(multipartFile);
// 上传文件到 OSS
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
ossClient.putObject(bucketName, fileName, inputStream);
// 文件访问路径
String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;
// 关闭ossClient
ossClient.shutdown();
return url; // 把上传到oss的路径返回
}
/**
* + + UUID 6 +
*/
private String generateFileName(MultipartFile multipartFile) {
// 获取原始文件名和后缀
String originalFilename = multipartFile.getOriginalFilename();
String fileExtension = null;
if (originalFilename != null) {
fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
}
// 获取当前时间戳并格式化到毫秒
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
String timestamp = dateFormat.format(new Date());
// 生成 UUID 并取前 6 位
String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 6); // 取前 6 位
// 组合新的文件名
return originalFilename.substring(0, originalFilename.lastIndexOf("."))
+ "_" + timestamp
+ "_" + uuid
+ fileExtension;
}
/**
*
*/
public String initMultipartUpload(String uploadId, int partNumber, InputStream inputStream, String originalFilename) {
//上传文件到 OSS
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
// 创建InitiateMultipartUploadRequest对象。
InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, fileName);
// 初始化分片。
InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
// 关闭ossClient
ossClient.shutdown();
return upresult.getUploadId();
}
/**
*
*/
public PartETag uploadPart(String uploadId, int partNumber, String filename, MultipartFile multipartFile, long startPos, long curPartSize, int i, String originalFilename) throws IOException {
//上传文件到 OSS
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 创建UploadPartRequest对象。
UploadPartRequest uploadPartRequest = new UploadPartRequest();
uploadPartRequest.setBucketName(bucketName);
uploadPartRequest.setKey(filename);
uploadPartRequest.setUploadId(uploadId);
// 设置上传的分片流。
// 以本地文件为例说明如何创建FIleInputstream并通过InputStream.skip()方法跳过指定数据。
InputStream instream = multipartFile.getInputStream();
instream.skip(startPos);
uploadPartRequest.setInputStream(instream);
// 设置分片大小。除了最后一个分片没有大小限制其他的分片最小为100 KB。
uploadPartRequest.setPartSize(curPartSize);
// 设置分片号。每一个上传的分片都有一个分片号取值范围是1~10000如果超出此范围OSS将返回InvalidArgument错误码。
uploadPartRequest.setPartNumber(i + 1);
// 每个分片不需要按顺序上传甚至可以在不同客户端上传OSS会按照分片号排序组成完整的文件。
UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
// 关闭ossClient
ossClient.shutdown();
// 每次上传分片之后OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
return uploadPartResult.getPartETag();
}
/**
*
*/
public String completeMultipartUpload(String originalFilename, String uploadId, List<PartETag> partETags) {
//上传文件到 OSS
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 创建CompleteMultipartUploadRequest对象。
// 在执行完成分片上传操作时需要提供所有有效的partETags。OSS收到提交的partETags后会逐一验证每个分片的有效性。当所有的数据分片验证通过后OSS将把这些分片组合成一个完整的文件。
String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
CompleteMultipartUploadRequest completeMultipartUploadRequest =
new CompleteMultipartUploadRequest(bucketName, fileName, uploadId, partETags);
// 完成分片上传。
CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
//文件访问路径
String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;
// 关闭ossClient
ossClient.shutdown();
return url;// 把上传到oss的路径返回
}
/**
* URL OSS
*
* @param fileUrl URL
* @return
*/
public boolean deleteFileByUrl(String fileUrl) {
// 检查 URL 是否包含正确的 OSS 格式
if (!fileUrl.contains(bucketName) || !fileUrl.contains(endpoint)) {
System.out.println("URL 格式错误,不属于当前 OSS 存储桶");
return false;
}
// 从 URL 中提取文件路径和文件名
String fileKey = fileUrl.substring(fileUrl.indexOf(bucketName) + bucketName.length() + 1);
// 创建 OSS 客户端实例
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
// 删除指定文件
ossClient.deleteObject(bucketName, fileKey);
return true;
} catch (Exception e) {
System.out.println("删除文件失败: " + e.getMessage());
return false;
} finally {
// 关闭 OSS 客户端
ossClient.shutdown();
}
}
}

@ -1,13 +0,0 @@
package com.flyingpig.cloudmusic.file.config;
import com.flyingpig.cloudmusic.file.AliOSSUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AutoFileConfiguration {
@Bean
public AliOSSUtils aliOSSUtils() {
return new AliOSSUtils();
}
}

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.flyingpig.cloudmusic.file.config.AutoFileConfiguration

@ -1,33 +0,0 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>common-idempotent</artifactId>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common-cache</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common-security</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

@ -1,71 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.annotation;
import com.flyingpig.cloudmusic.idempotent.enums.IdempotentSceneEnum;
import com.flyingpig.cloudmusic.idempotent.enums.IdempotentTypeEnum;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Idempotent {
/**
* Key {@link Idempotent#type()} {@link IdempotentTypeEnum#SPEL}
*/
String key() default "";
/**
*
*/
String message() default "您操作太快,请稍后再试";
/**
*
* RestAPI 使 {@link IdempotentTypeEnum#TOKEN} {@link IdempotentTypeEnum#PARAM}
* 使 {@link IdempotentTypeEnum#SPEL}
*/
IdempotentTypeEnum type() default IdempotentTypeEnum.PARAM;
/**
* {@link IdempotentSceneEnum}
*/
IdempotentSceneEnum scene() default IdempotentSceneEnum.RESTAPI;
/**
* Key MQ
* {@link IdempotentSceneEnum#MQ} and {@link IdempotentTypeEnum#SPEL}
*/
String uniqueKeyPrefix() default "";
/**
* Key 1 MQ
* {@link IdempotentSceneEnum#MQ} and {@link IdempotentTypeEnum#SPEL}
*/
long keyTimeout() default 3600L;
}

@ -1,68 +0,0 @@
package com.flyingpig.cloudmusic.idempotent.config;
import com.flyingpig.cloudmusic.cache.StringCacheUtil;
import com.flyingpig.cloudmusic.idempotent.core.IdempotentAspect;
import com.flyingpig.cloudmusic.idempotent.core.bases.ApplicationContextHolder;
import com.flyingpig.cloudmusic.idempotent.core.param.IdempotentParamExecuteHandler;
import com.flyingpig.cloudmusic.idempotent.core.param.IdempotentParamService;
import com.flyingpig.cloudmusic.idempotent.core.spel.IdempotentSpELByMQExecuteHandler;
import com.flyingpig.cloudmusic.idempotent.core.spel.IdempotentSpELByRestAPIExecuteHandler;
import com.flyingpig.cloudmusic.idempotent.core.spel.IdempotentSpELService;
import org.redisson.api.RedissonClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
/**
*
*/
@EnableConfigurationProperties(IdempotentProperties.class)
public class IdempotentAutoConfiguration {
/**
*
*/
@Bean
public IdempotentAspect idempotentAspect() {
return new IdempotentAspect();
}
/**
* RestAPI
*/
@Bean
@ConditionalOnMissingBean
public IdempotentParamService idempotentParamExecuteHandler(RedissonClient redissonClient) {
return new IdempotentParamExecuteHandler(redissonClient);
}
/**
* SpEL RestAPI
*/
@Bean
@ConditionalOnMissingBean
public IdempotentSpELService idempotentSpELByRestAPIExecuteHandler(RedissonClient redissonClient) {
return new IdempotentSpELByRestAPIExecuteHandler(redissonClient);
}
/**
* SpEL MQ
*/
@Bean
@ConditionalOnMissingBean
public IdempotentSpELByMQExecuteHandler idempotentSpELByMQExecuteHandler(StringCacheUtil stringCacheUtil, StringRedisTemplate stringRedisTemplate) {
return new IdempotentSpELByMQExecuteHandler(stringCacheUtil, stringRedisTemplate);
}
/**
*
* 12306
*/
@Bean
@ConditionalOnMissingBean
public ApplicationContextHolder congoApplicationContextHolder() {
return new ApplicationContextHolder();
}
}

@ -1,16 +0,0 @@
package com.flyingpig.cloudmusic.idempotent.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.concurrent.TimeUnit;
/**
*
*/
@Data
@ConfigurationProperties(prefix = IdempotentProperties.PREFIX)
public class IdempotentProperties {
public static final String PREFIX = "flyingpig.idempotent.token";
}

@ -1,28 +0,0 @@
package com.flyingpig.cloudmusic.idempotent.core;
import com.flyingpig.cloudmusic.idempotent.annotation.Idempotent;
import org.aspectj.lang.ProceedingJoinPoint;
public abstract class AbstractIdempotentExecuteHandler implements IdempotentExecuteHandler {
/**
*
*
* @param joinPoint AOP
* @return
*/
protected abstract IdempotentParamWrapper buildWrapper(ProceedingJoinPoint joinPoint);
/**
*
*
* @param joinPoint AOP
* @param idempotent
*/
public void execute(ProceedingJoinPoint joinPoint, Idempotent idempotent) {
// 模板方法模式:构建幂等参数包装器
IdempotentParamWrapper idempotentParamWrapper = buildWrapper(joinPoint).setIdempotent(idempotent);
handler(idempotentParamWrapper);
}
}

@ -1,60 +0,0 @@
package com.flyingpig.cloudmusic.idempotent.core;
import com.flyingpig.cloudmusic.idempotent.annotation.Idempotent;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
/**
* AOP
*/
@Aspect
public final class IdempotentAspect {
/**
*
*/
@Around("@annotation(com.flyingpig.cloudmusic.idempotent.annotation.Idempotent)")
public Object idempotentHandler(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取幂等注解
Idempotent idempotent = getIdempotent(joinPoint);
// 根据幂等注解的场景(http or mq)和实现类型(三种实现类型)获取对应实例
IdempotentExecuteHandler instance = IdempotentExecuteHandlerFactory.getInstance(idempotent.scene(), idempotent.type());
Object resultObj;
try {
// 执行实例
instance.execute(joinPoint, idempotent);
// 执行原有方法流程
resultObj = joinPoint.proceed();
// 实例后置处理
instance.postProcessing();
} catch (RepeatConsumptionException ex) {
/*
*
*
* 1. 便 RocketMQ
* 2.
*/
if (!ex.getError()) {
return null;
}
throw ex;
} catch (Throwable ex) {
// 客户端消费存在异常,需要删除幂等标识方便下次 RocketMQ 再次通过重试队列投递
instance.exceptionProcessing();
throw ex;
} finally {
IdempotentContext.clean();
}
return resultObj;
}
public static Idempotent getIdempotent(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method targetMethod = joinPoint.getTarget().getClass().getDeclaredMethod(methodSignature.getName(), methodSignature.getMethod().getParameterTypes());
return targetMethod.getAnnotation(Idempotent.class);
}
}

@ -1,62 +0,0 @@
package com.flyingpig.cloudmusic.idempotent.core;
import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.Maps;
import java.util.Map;
/**
*
*/
public final class IdempotentContext {
private static final ThreadLocal<Map<String, Object>> CONTEXT = new ThreadLocal<>();
public static Map<String, Object> get() {
return CONTEXT.get();
}
public static Object getKey(String key) {
Map<String, Object> context = get();
if (CollUtil.isNotEmpty(context)) {
return context.get(key);
}
return null;
}
public static String getString(String key) {
Object actual = getKey(key);
if (actual != null) {
return actual.toString();
}
return null;
}
public static void put(String key, Object val) {
// 获得上下文
Map<String, Object> context = get();
if (CollUtil.isEmpty(context)) {
// 如果为空,创建一个新的上下文
context = Maps.newHashMap();
}
// 锁
context.put(key, val);
putContext(context);
}
public static void putContext(Map<String, Object> context) {
// 获得上下文
Map<String, Object> threadContext = CONTEXT.get();
if (CollUtil.isNotEmpty(threadContext)) {
// 如果不为空,放入所有的值
threadContext.putAll(context);
return;
}
// 设置上下文
CONTEXT.set(context);
}
public static void clean() {
CONTEXT.remove();
}
}

@ -1,39 +0,0 @@
package com.flyingpig.cloudmusic.idempotent.core;
import com.flyingpig.cloudmusic.idempotent.annotation.Idempotent;
import org.aspectj.lang.ProceedingJoinPoint;
/**
*
*/
public interface IdempotentExecuteHandler {
/**
*
*
* @param wrapper
*/
void handler(IdempotentParamWrapper wrapper);
/**
*
*
* @param joinPoint AOP
* @param idempotent
*/
void execute(ProceedingJoinPoint joinPoint, Idempotent idempotent);
/**
*
*/
default void exceptionProcessing() {
}
/**
*
*/
default void postProcessing() {
}
}

@ -1,64 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.core;
import com.flyingpig.cloudmusic.idempotent.core.bases.ApplicationContextHolder;
import com.flyingpig.cloudmusic.idempotent.core.param.IdempotentParamService;
import com.flyingpig.cloudmusic.idempotent.core.spel.IdempotentSpELByMQExecuteHandler;
import com.flyingpig.cloudmusic.idempotent.core.spel.IdempotentSpELByRestAPIExecuteHandler;
import com.flyingpig.cloudmusic.idempotent.enums.IdempotentSceneEnum;
import com.flyingpig.cloudmusic.idempotent.enums.IdempotentTypeEnum;
/**
*
*/
public final class IdempotentExecuteHandlerFactory {
/**
*
*
* @param scene
* @param type
* @return
*/
public static IdempotentExecuteHandler getInstance(IdempotentSceneEnum scene, IdempotentTypeEnum type) {
IdempotentExecuteHandler result = null;
switch (scene) {
case RESTAPI:
switch (type) {
case PARAM:
result = ApplicationContextHolder.getBean(IdempotentParamService.class);
break;
case SPEL:
result = ApplicationContextHolder.getBean(IdempotentSpELByRestAPIExecuteHandler.class);
break;
default:
break;
}
break;
case MQ:
result = ApplicationContextHolder.getBean(IdempotentSpELByMQExecuteHandler.class);
break;
default:
break;
}
return result;
}
}

@ -1,52 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.core;
import com.flyingpig.cloudmusic.idempotent.annotation.Idempotent;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.aspectj.lang.ProceedingJoinPoint;
/**
*
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public final class IdempotentParamWrapper {
/**
*
*/
private Idempotent idempotent;
/**
* AOP
*/
private ProceedingJoinPoint joinPoint;
/**
*
*/
private String lockKey;
}

@ -1,38 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.core;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
*
*/
@RequiredArgsConstructor
public class RepeatConsumptionException extends RuntimeException {
/**
*
* <p>
*
* 1. 便 RocketMQ
* 2.
*/
@Getter
private final Boolean error;
}

@ -1,63 +0,0 @@
package com.flyingpig.cloudmusic.idempotent.core.bases;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import java.lang.annotation.Annotation;
import java.util.Map;
/**
* Application context holder.
*/
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext CONTEXT;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextHolder.CONTEXT = applicationContext;
}
/**
* Get ioc container bean by type.
*/
public static <T> T getBean(Class<T> clazz) {
return CONTEXT.getBean(clazz);
}
/**
* Get ioc container bean by name.
*/
public static Object getBean(String name) {
return CONTEXT.getBean(name);
}
/**
* Get ioc container bean by name and type.
*/
public static <T> T getBean(String name, Class<T> clazz) {
return CONTEXT.getBean(name, clazz);
}
/**
* Get a set of ioc container beans by type.
*/
public static <T> Map<String, T> getBeansOfType(Class<T> clazz) {
return CONTEXT.getBeansOfType(clazz);
}
/**
* Find whether the bean has annotations.
*/
public static <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType) {
return CONTEXT.findAnnotationOnBean(beanName, annotationType);
}
/**
* Get application context.
*/
public static ApplicationContext getInstance() {
return CONTEXT;
}
}

@ -1,102 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.core.param;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson2.JSON;
import com.flyingpig.cloudmusic.idempotent.core.AbstractIdempotentExecuteHandler;
import com.flyingpig.cloudmusic.idempotent.core.IdempotentContext;
import com.flyingpig.cloudmusic.idempotent.core.IdempotentParamWrapper;
import com.flyingpig.cloudmusic.security.util.UserContext;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
*
*/
@RequiredArgsConstructor
public final class IdempotentParamExecuteHandler extends AbstractIdempotentExecuteHandler implements IdempotentParamService {
private final RedissonClient redissonClient;
private final static String LOCK = "lock:param:restAPI";
@Override
protected IdempotentParamWrapper buildWrapper(ProceedingJoinPoint joinPoint) {
String lockKey = String.format("idempotent:path:%s:currentUserId:%s:md5:%s", getServletPath(), getCurrentUserId(), calcArgsMD5(joinPoint));
return IdempotentParamWrapper.builder().lockKey(lockKey).joinPoint(joinPoint).build();
}
/**
* @return 线 ServletPath
*/
private String getServletPath() {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return sra.getRequest().getServletPath();
}
/**
* @return ID
*/
private String getCurrentUserId() {
String userId = String.valueOf(UserContext.getUser().getUserId());
if(StrUtil.isBlank(userId)){
throw new RuntimeException("用户ID获取失败请登录");
}
return userId;
}
/**
* @return joinPoint md5
*/
private String calcArgsMD5(ProceedingJoinPoint joinPoint) {
return DigestUtil.md5Hex(JSON.toJSONBytes(joinPoint.getArgs()));
}
@Override
public void handler(IdempotentParamWrapper wrapper) {
String lockKey = wrapper.getLockKey();
RLock lock = redissonClient.getLock(lockKey);
if (!lock.tryLock()) {
throw new RuntimeException(wrapper.getIdempotent().message());
}
IdempotentContext.put(LOCK, lock);
}
@Override
public void postProcessing() {
RLock lock = null;
try {
lock = (RLock) IdempotentContext.getKey(LOCK);
} finally {
if (lock != null) {
lock.unlock();
}
}
}
@Override
public void exceptionProcessing() {
postProcessing();
}
}

@ -1,28 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.core.param;
import com.flyingpig.cloudmusic.idempotent.core.IdempotentExecuteHandler;
/**
*
* 12306
*/
public interface IdempotentParamService extends IdempotentExecuteHandler {
}

@ -1,113 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.core.spel;
import com.flyingpig.cloudmusic.cache.StringCacheUtil;
import com.flyingpig.cloudmusic.idempotent.annotation.Idempotent;
import com.flyingpig.cloudmusic.idempotent.core.*;
import com.flyingpig.cloudmusic.idempotent.enums.IdempotentMQConsumeStatusEnum;
import com.flyingpig.cloudmusic.idempotent.toolkit.LogUtil;
import com.flyingpig.cloudmusic.idempotent.toolkit.SpELUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* SpEL MQ
*/
@RequiredArgsConstructor
public final class IdempotentSpELByMQExecuteHandler extends AbstractIdempotentExecuteHandler implements IdempotentSpELService {
private final static int TIMEOUT = 600;
private final static String WRAPPER = "wrapper:spEL:MQ";
private final static String LUA_SCRIPT_SET_IF_ABSENT_AND_GET_PATH = "lua/set_if_absent_and_get.lua";
private final StringCacheUtil stringCacheUtil;
private final StringRedisTemplate stringRedisTemplate;
@SneakyThrows
@Override
protected IdempotentParamWrapper buildWrapper(ProceedingJoinPoint joinPoint) {
Idempotent idempotent = IdempotentAspect.getIdempotent(joinPoint);
String key = (String) SpELUtil.parseKey(idempotent.key(), ((MethodSignature) joinPoint.getSignature()).getMethod(), joinPoint.getArgs());
return IdempotentParamWrapper.builder().lockKey(key).joinPoint(joinPoint).build();
}
@Override
public void handler(IdempotentParamWrapper wrapper) {
String uniqueKey = wrapper.getIdempotent().uniqueKeyPrefix() + wrapper.getLockKey();
String absentAndGet = this.setIfAbsentAndGet(uniqueKey, IdempotentMQConsumeStatusEnum.CONSUMING.getCode(), TIMEOUT, TimeUnit.SECONDS);
if (Objects.nonNull(absentAndGet)) {
boolean error = IdempotentMQConsumeStatusEnum.isError(absentAndGet);
LogUtil.getLog(wrapper.getJoinPoint()).warn("[{}] MQ repeated consumption, {}.", uniqueKey, error ? "Wait for the client to delay consumption" : "Status is completed");
throw new RepeatConsumptionException(error);
}
IdempotentContext.put(WRAPPER, wrapper);
}
public String setIfAbsentAndGet(String key, String value, long timeout, TimeUnit timeUnit) {
DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
ClassPathResource resource = new ClassPathResource(LUA_SCRIPT_SET_IF_ABSENT_AND_GET_PATH);
redisScript.setScriptSource(new ResourceScriptSource(resource));
redisScript.setResultType(String.class);
long millis = timeUnit.toMillis(timeout);
return stringRedisTemplate.execute(redisScript, List.of(key), value, String.valueOf(millis));
}
@Override
public void exceptionProcessing() {
IdempotentParamWrapper wrapper = (IdempotentParamWrapper) IdempotentContext.getKey(WRAPPER);
if (wrapper != null) {
Idempotent idempotent = wrapper.getIdempotent();
String uniqueKey = idempotent.uniqueKeyPrefix() + wrapper.getLockKey();
try {
stringCacheUtil.delete(uniqueKey);
} catch (Throwable ex) {
LogUtil.getLog(wrapper.getJoinPoint()).error("[{}] Failed to del MQ anti-heavy token.", uniqueKey);
}
}
}
@Override
public void postProcessing() {
IdempotentParamWrapper wrapper = (IdempotentParamWrapper) IdempotentContext.getKey(WRAPPER);
if (wrapper != null) {
Idempotent idempotent = wrapper.getIdempotent();
String uniqueKey = idempotent.uniqueKeyPrefix() + wrapper.getLockKey();
try {
stringCacheUtil.set(uniqueKey, IdempotentMQConsumeStatusEnum.CONSUMED.getCode(), idempotent.keyTimeout(), TimeUnit.SECONDS);
} catch (Throwable ex) {
LogUtil.getLog(wrapper.getJoinPoint()).error("[{}] Failed to set MQ anti-heavy token.", uniqueKey);
}
}
}
}

@ -1,65 +0,0 @@
package com.flyingpig.cloudmusic.idempotent.core.spel;
import com.flyingpig.cloudmusic.idempotent.annotation.Idempotent;
import com.flyingpig.cloudmusic.idempotent.core.AbstractIdempotentExecuteHandler;
import com.flyingpig.cloudmusic.idempotent.core.IdempotentAspect;
import com.flyingpig.cloudmusic.idempotent.core.IdempotentContext;
import com.flyingpig.cloudmusic.idempotent.core.IdempotentParamWrapper;
import com.flyingpig.cloudmusic.idempotent.toolkit.SpELUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
/**
* SpEL RestAPI
*/
@RequiredArgsConstructor
public final class IdempotentSpELByRestAPIExecuteHandler extends AbstractIdempotentExecuteHandler implements IdempotentSpELService {
private final RedissonClient redissonClient;
private final static String LOCK = "lock:spEL:restAPI";
@SneakyThrows
@Override
protected IdempotentParamWrapper buildWrapper(ProceedingJoinPoint joinPoint) {
Idempotent idempotent = IdempotentAspect.getIdempotent(joinPoint);
String key = (String) SpELUtil.parseKey(idempotent.key(), ((MethodSignature) joinPoint.getSignature()).getMethod(), joinPoint.getArgs());
return IdempotentParamWrapper.builder().lockKey(key).joinPoint(joinPoint).build();
}
@Override
public void handler(IdempotentParamWrapper wrapper) {
String uniqueKey = wrapper.getIdempotent().uniqueKeyPrefix() + wrapper.getLockKey();
RLock lock = redissonClient.getLock(uniqueKey);
if (!lock.tryLock()) {
throw new RuntimeException(wrapper.getIdempotent().message());
}
IdempotentContext.put(LOCK, lock);
}
private void releaseLock() {
RLock lock = null;
try {
lock = (RLock) IdempotentContext.getKey(LOCK);
} finally {
if (lock != null) {
lock.unlock();
}
}
}
@Override
public void postProcessing() {
releaseLock();
}
@Override
public void exceptionProcessing() {
releaseLock();
}
}

@ -1,27 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.core.spel;
import com.flyingpig.cloudmusic.idempotent.core.IdempotentExecuteHandler;
/**
* SpEL
*/
public interface IdempotentSpELService extends IdempotentExecuteHandler {
}

@ -1,53 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Objects;
/**
* MQ
*/
@RequiredArgsConstructor
public enum IdempotentMQConsumeStatusEnum {
/**
*
*/
CONSUMING("0"),
/**
*
*/
CONSUMED("1");
@Getter
private final String code;
/**
*
*
* @param consumeStatus
* @return
*/
public static boolean isError(String consumeStatus) {
return Objects.equals(CONSUMING.code, consumeStatus);
}
}

@ -1,34 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.enums;
/**
*
*/
public enum IdempotentSceneEnum {
/**
* RestAPI
*/
RESTAPI,
/**
* MQ
*/
MQ
}

@ -1,39 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.enums;
/**
*
*/
public enum IdempotentTypeEnum {
/**
* Token
*/
TOKEN,
/**
*
*/
PARAM,
/**
* SpEL
*/
SPEL
}

@ -1,37 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.toolkit;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*/
public class LogUtil {
/**
* Logger
*/
public static Logger getLog(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
return LoggerFactory.getLogger(methodSignature.getDeclaringType());
}
}

@ -1,79 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.toolkit;
import cn.hutool.core.util.ArrayUtil;
import com.google.common.collect.Lists;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Optional;
/**
* SpEL
*/
public class SpELUtil {
/**
* 使 spEL
*
* @param spEl spEL
* @return 使 spEL
*/
public static Object parseKey(String spEl, Method method, Object[] contextObj) {
// 校验,是否包含 # 或 T(,如果包含则需要解析
ArrayList<String> spELFlag = Lists.newArrayList("#", "T(");
Optional<String> optional = spELFlag.stream().filter(spEl::contains).findFirst();
if (optional.isPresent()) {
// 解析并返回
return parse(spEl, method, contextObj);
}
// 直接返回
return spEl;
}
/**
*
*
* @param spEl spEl
* @param contextObj
* @return
*/
public static Object parse(String spEl, Method method, Object[] contextObj) {
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
ExpressionParser parser = new SpelExpressionParser();
// 解析 SpEL 表达式
Expression exp = parser.parseExpression(spEl);
// 获取方法参数名
String[] params = discoverer.getParameterNames(method);
StandardEvaluationContext context = new StandardEvaluationContext();
if (ArrayUtil.isNotEmpty(params)) {
// 设置参数
for (int len = 0; len < params.length; len++) {
context.setVariable(params[len], contextObj[len]);
}
}
// 解析并返回
return exp.getValue(context);
}
}

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.flyingpig.cloudmusic.idempotent.config.IdempotentAutoConfiguration

@ -1,6 +0,0 @@
-- 原子性获取给定key若key存在返回其值若key不存在则设置key并返回null
local key = KEYS[1]
local value = ARGV[1]
local expire_time_ms = ARGV[2]
return redis.call('SET', key, value, 'NX', 'GET', 'PX', expire_time_ms)

@ -1,23 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>cloud-music</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>common</artifactId>
<packaging>pom</packaging>
<modules>
<module>/security</module>
<module>/file</module>
<module>/cache</module>
<module>/sentinel</module>
<module>/web</module>
</modules>
</project>

@ -1,33 +0,0 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>common-security</artifactId>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common-web</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
</dependencies>
</project>

@ -1,30 +0,0 @@
package com.flyingpig.cloudmusic.security.aop;
import com.flyingpig.cloudmusic.security.util.UserContext;
import com.flyingpig.cloudmusic.web.Result;
import com.flyingpig.cloudmusic.web.StatusCode;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class AuthorizationAspect {
@Around("@annotation(beforeAuthorize)")
public Object checkAuthorization(ProceedingJoinPoint proceedingJoinPoint, BeforeAuthorize beforeAuthorize) throws Throwable {
String role = UserContext.getUser().getRole();
String requiredRoles = beforeAuthorize.role();
if (!role.equals(requiredRoles)) {
return Result.error(StatusCode.UNAUTHORIZED, "权限不足");
} else {
return proceedingJoinPoint.proceed();
}
}
}

@ -1,12 +0,0 @@
package com.flyingpig.cloudmusic.security.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BeforeAuthorize {
String role();
}

@ -1,14 +0,0 @@
package com.flyingpig.cloudmusic.security.config;
import com.flyingpig.cloudmusic.security.interceptor.UserInfoInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInfoInterceptor());
}
}

@ -1,14 +0,0 @@
package com.flyingpig.cloudmusic.security.interceptor;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LocalUserInfo {
Long userId;
String role;
}

@ -1,37 +0,0 @@
package com.flyingpig.cloudmusic.security.interceptor;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.flyingpig.cloudmusic.security.util.UserContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class UserInfoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取请求头中的用户信息
String userId = request.getHeader("userId");
String role = request.getHeader("userRole");
log.info("用户 {} 访问接口: {} {}", userId, request.getMethod(), request.getRequestURI());
// 2.判断是否为空
if (StringUtil.isNotBlank(userId)) {
// 不为空保存到ThreadLocal
UserContext.setUser(new LocalUserInfo(Long.valueOf(userId), role));
}
// 3.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 移除用户
UserContext.removeUser();
}
}

@ -1,127 +0,0 @@
package com.flyingpig.cloudmusic.security.util;
import io.jsonwebtoken.*;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 30*24*60 * 60 * 1000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "Zmx5aW5ncGln";//flyingpig的base64编码
/**
* secretKey
*
* @return
*/
public static SecretKey generalKey() {
//将密钥明文base64解码后通过aes算法进行加密
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
public static String getUUID() {
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
/**
* jtw
*
* @param subject tokenjson
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}
/**
* jtw
*
* @param subject tokenjson
* @param ttlMillis token
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
return builder.compact();
}
/**
* jtw
*
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String subject, Long ttlMillis,String uuid) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, uuid);// 设置过期时间
return builder.compact();
}
//三个生成jwt方法中的核心代码
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
//签名算法和密钥
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
//求出过期时间
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if (ttlMillis == null) {
ttlMillis = JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
//set方法都是设置payload的json然后进行签名
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("flyingpig") // 签发者
.setIssuedAt(now) // 签发时间
.setExpiration(expDate) //过期时间
.signWith(signatureAlgorithm, secretKey); //使用HS256对称加密算法签名, 第二个参数为秘钥
}
//解析JWT令牌的payload自定义信息
public static Claims parseJwt(String jwt) {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
//获取payload中的uuid
public static String getUUIDFromJWT(String jwt) {
Claims claims = parseJwt(jwt);
return claims.getId();
}
//获取payload中存放的subject即string版的userId
public static String getSubjectFromJWT(String jwt) {
Claims claims = parseJwt(jwt);
return claims.getSubject();
}
//进行base64编码的测试类
public static void main(String[] args) throws Exception {
String jwtKey = "flyingpig";
String encodedKey = Base64.getEncoder().encodeToString(jwtKey.getBytes());
System.out.println(encodedKey);
}
}

@ -1,22 +0,0 @@
package com.flyingpig.cloudmusic.security.util;
import com.flyingpig.cloudmusic.security.interceptor.LocalUserInfo;
public class UserContext {
private static final ThreadLocal<LocalUserInfo> tl =new ThreadLocal<>();
//保存当前登录用户信息到ThreadLocal
public static void setUser(LocalUserInfo localUserInfo){
tl.set(localUserInfo);
}
//获取当前登录的用户信息
public static LocalUserInfo getUser(){
return tl.get();
}
//移除当前登录用户信息
public static void removeUser(){
tl.remove();
}
}

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.flyingpig.cloudmusic.security.config.MvcConfig

@ -1,33 +0,0 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>common-sentinel</artifactId>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

@ -1,21 +0,0 @@
package com.flyingpig.cloudmusic.sentinel;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
public class HeaderOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
// 1.获取请求头
String origin = request.getHeader("origin");
// 2.非空判断
if (StringUtils.isEmpty(origin)) {
origin = "blank";
}
return origin;
}
}

@ -1,13 +0,0 @@
package com.flyingpig.cloudmusic.sentinel.exception;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface BlockExceptionHandler {
/**
* BlockException
*/
void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
}

@ -1,35 +0,0 @@
package com.flyingpig.cloudmusic.sentinel.exception;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
String msg = "未知异常";
int status = 429;
if (e instanceof FlowException) {
msg = "请求被限流了";
} else if (e instanceof ParamFlowException) {
msg = "请求被热点参数限流";
} else if (e instanceof DegradeException) {
msg = "请求被降级了";
} else if (e instanceof AuthorityException) {
msg = "没有权限访问";
status = 401;
}
response.setContentType("application/json;charset=utf-8");
response.setStatus(status);
response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
}
}

@ -1,33 +0,0 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>common-web</artifactId>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.5.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>io.projectreactor.addons</groupId>
<artifactId>reactor-extra</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version> <!-- 版本号可根据需要调整 -->
<scope>provided</scope>
</dependency>
</dependencies>
</project>

@ -1,34 +0,0 @@
package com.flyingpig.cloudmusic.web;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private Integer code;//响应码token过期返回0正确返回1异常返回2
private String msg;//响应信息,描述字符串
private Object data;//返回的数据
//增删改
public static Result success() {
return new Result(StatusCode.OK, "success", null);
}
//查询 成功响应
public static Result success(Object data) {
return new Result(StatusCode.OK, "success", data);
}
//失败响应
public static Result error(Integer code,String msg) {
return new Result(code, msg, null);
}
public static Result error(String msg) {
return new Result(StatusCode.SERVERERROR, msg, null);
}
}

@ -1,34 +0,0 @@
package com.flyingpig.cloudmusic.web;
public class StatusCode {
/**
*
*/
public static final int OK = 200;
/**
*
*/
public static final int PARAMETERERROR = 400;
/**
*
*/
public static final int NOTFOUND = 404;
/**
*
*/
public static final int METHODERROR = 405;
/**
*
*/
public static final int SERVERERROR = 500;
/**
*
*/
public static final int UNAUTHORIZED = 401;
}

@ -1,51 +0,0 @@
package com.flyingpig.cloudmusic.web.config;
import com.flyingpig.cloudmusic.web.exception.GlobalExceptionHandler;
import com.flyingpig.cloudmusic.web.init.InitializeDispatcherServletController;
import com.flyingpig.cloudmusic.web.init.InitializeDispatcherServletHandler;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
public class WebAutoConfiguration {
public final static String INITIALIZE_PATH = "/initialize/dispatcher-servlet";
@Bean
@ConditionalOnMissingBean// 只有在 Spring 容器中没有定义特定类型的 Bean 时,才会创建该 Bean
@ConditionalOnMissingClass("org.springframework.web.reactive.DispatcherHandler") // 检查 WebFlux 是否存在
public GlobalExceptionHandler globalExceptionHandler() {
return new GlobalExceptionHandler();
}
@Bean
@ConditionalOnMissingClass("org.springframework.web.reactive.DispatcherHandler") // 检查 WebFlux 是否存在
public InitializeDispatcherServletController initializeDispatcherServletController() {
return new InitializeDispatcherServletController();
}
@Bean
@ConditionalOnMissingClass("org.springframework.web.reactive.DispatcherHandler") // 检查 WebFlux 是否存在
public RestTemplate simpleRestTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
@Bean
@ConditionalOnMissingClass("org.springframework.web.reactive.DispatcherHandler") // 检查 WebFlux 是否存在
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(5000);
factory.setConnectTimeout(5000);
return factory;
}
@Bean
@ConditionalOnMissingClass("org.springframework.web.reactive.DispatcherHandler") // 检查 WebFlux 是否存在
public InitializeDispatcherServletHandler initializeDispatcherServletHandler(RestTemplate simpleRestTemplate, ConfigurableEnvironment configurableEnvironment) {
return new InitializeDispatcherServletHandler(simpleRestTemplate, configurableEnvironment);
}
}

@ -1,68 +0,0 @@
package com.flyingpig.cloudmusic.web.exception;
import com.flyingpig.cloudmusic.web.Result;
import com.flyingpig.cloudmusic.web.StatusCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import java.net.BindException;
//全局异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* /--400
*
* @param e
* @return
*/
@ExceptionHandler({
MissingServletRequestParameterException.class,
MethodArgumentTypeMismatchException.class,
BindException.class}
)
public Result missingServletRequestParameterException(Exception e) {
return Result.error(StatusCode.PARAMETERERROR, "缺少参数或参数错误");
}
/**
* --405
*
* @param e
* @return
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Result httpRequestMethodNotSupportedExceptionHandler(Exception e) {
log.error("请求方法错误");
return Result.error(StatusCode.METHODERROR, "请求方法错误");
}
/**
* Redis--500
*/
@ExceptionHandler(RedisConnectionFailureException.class)
public Result edisConnectionFailureExceptionHandler(RedisConnectionFailureException e) {
log.error("redis连接错误啦啦啦啦啦啦");
return Result.error(StatusCode.SERVERERROR, "redis连接错误啦啦啦啦啦啦");
}
/**
* --500
*/
@ExceptionHandler(Exception.class)
public Result exceptionHandler(Exception e) {
e.printStackTrace();
return Result.error(StatusCode.SERVERERROR, e.toString());
}
}

@ -1,18 +0,0 @@
package com.flyingpig.cloudmusic.web.init;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static com.flyingpig.cloudmusic.web.config.WebAutoConfiguration.INITIALIZE_PATH;
@Slf4j(topic = "Initialize DispatcherServlet")
@RestController
public final class InitializeDispatcherServletController {
@GetMapping(INITIALIZE_PATH)
public void initializeDispatcherServlet() {
log.info("Initialized the dispatcherServlet to improve the first response time of the interface...");
}
}

@ -1,31 +0,0 @@
package com.flyingpig.cloudmusic.web.init;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.DispatcherServlet;
import static com.flyingpig.cloudmusic.web.config.WebAutoConfiguration.INITIALIZE_PATH;
@RequiredArgsConstructor
public final class InitializeDispatcherServletHandler implements CommandLineRunner {
private final RestTemplate restTemplate;
private final ConfigurableEnvironment configurableEnvironment;
@Override
public void run(String... args) throws Exception {
String url = String.format("http://127.0.0.1:%s%s",
configurableEnvironment.getProperty("server.port", "8080") + configurableEnvironment.getProperty("server.servlet.context-path", ""),
INITIALIZE_PATH);
try {
restTemplate.execute(url, HttpMethod.GET, null, null);
} catch (Throwable ignored) {
}
}
}

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.flyingpig.cloudmusic.web.config.WebAutoConfiguration

@ -1,8 +0,0 @@
nohup java -jar gateway-1.0-SNAPSHOT.jar > gateway.log 2>&1 &
nohup java -jar auth-service-1.0-SNAPSHOT.jar > auth-service.log 2>&1 &
nohup java -jar music-related-service-1.0-SNAPSHOT.jar > music-related-service.log 2>&1 &
nohup java -jar music-service-1.0-SNAPSHOT.jar > music-service.log 2>&1 &
nohup java -jar songlist-service-1.0-SNAPSHOT.jar > songlist-service.log 2>&1 &
# 除了上面服务的启动还需要安装基础部件mysql redis rabbitmq nacos
# 可选项es xxl-job

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save