Compare commits

...

No commits in common. "main" and "v2" have entirely different histories.
main ... v2

179 changed files with 1751 additions and 2279 deletions

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
/gradlew text eol=lf
*.bat text eol=crlf
*.jar binary

30
.github/workflows/ci.yaml vendored Normal file
View File

@ -0,0 +1,30 @@
name: ci
on: [pull_request, push, workflow_dispatch]
jobs:
build:
strategy:
matrix:
java: [17]
os: [ubuntu-22.04]
runs-on: ${{ matrix.os }}
steps:
- name: checkout repository
uses: actions/checkout@v4
- name: setup jdk ${{ matrix.java }}
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java }}
distribution: 'microsoft'
- name: make gradle wrapper executable
if: ${{ runner.os != 'Windows' }}
run: chmod +x ./gradlew
- name: setup proxy
run: echo "systemProp.http.proxyHost=192.168.5.6\\nsystemProp.http.proxyPort=7890\\nsystemProp.https.proxyHost=192.168.5.6\\nsystemProp.https.proxyPort=7890" >> gradle.properties
- name: build
run: _JAVA_OPTIONS="-Dhttp.proxyHost=192.168.5.6 -Dhttp.proxyPort=7890 -Dhttps.proxyHost=192.168.5.6 -Dhttps.proxyPort=7890" ./gradlew clean bootJar --no-daemon
- name: capture build artifacts
uses: actions/upload-artifact@v3
with:
name: Artifacts
path: build/libs/

176
.gitignore vendored
View File

@ -1,144 +1,42 @@
# ---> Java HELP.md
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
# ---> JetBrains
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# ---> VisualStudioCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
# ---> Gradle
.gradle .gradle
**/build/ build/
!src/**/build/ !gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
# Ignore Gradle GUI config ### STS ###
gradle-app.setting .apt_generated
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Avoid ignore Gradle wrappper properties
!gradle-wrapper.properties
# Cache of project
.gradletasknamecache
# Eclipse Gradle plugin generated files
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath .classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
# Running ### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
run/ run/
build*
run*

Binary file not shown.

8
.idea/.gitignore generated vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

1
.idea/.name generated
View File

@ -1 +0,0 @@
clubs

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ApifoxUploaderProjectSetting">
<option name="apiAccessToken" value="APS-QWelq6AktQqByilNWeWZgKoPT7a4ml1d" />
<option name="apiProjectIds">
<array>
<option value="&lt;byte-array&gt;rO0ABXNyADZjb20uaXRhbmdjZW50LmlkZWEucGx1Z2luLmFwaS5hY2NvdW50LlByb2plY3RBbmRNb2R1bGUAAAAAAAAAAQIAFVoABmVuYWJsZUwACG1vZHVsZUlkdAASTGphdmEvbGFuZy9TdHJpbmc7TAAGb3RoZXIxcQB+AAFMAAdvdGhlcjEwcQB+AAFMAAdvdGhlcjExcQB+AAFMAAdvdGhlcjEycQB+AAFMAAZvdGhlcjJxAH4AAUwABm90aGVyM3EAfgABTAAGb3RoZXI0cQB+AAFMAAZvdGhlcjVxAH4AAUwABm90aGVyNnEAfgABTAAGb3RoZXI3cQB+AAFMAAZvdGhlcjhxAH4AAUwABm90aGVyOXEAfgABTAAKcGF0aEJlZm9yZXEAfgABTAANcHJvamVjdEZvbGRlcnEAfgABTAAPcHJvamVjdEZvbGRlcklkcQB+AAFMAAlwcm9qZWN0SWRxAH4AAUwAC3Byb2plY3ROYW1lcQB+AAFMAAxzY2hlbWFGb2xkZXJxAH4AAUwACHNjaGVtYUlkcQB+AAF4cAB0AAZoaWRkZW5wcHBwcHBwcHBwcHB0AApwYXRoQmVmb3JldAANcHJvamVjdEZvbGRlcnQAD3Byb2plY3RGb2xkZXJJZHQAC3Byb2plY3ROYW1ldAAIaGlkZGVuSWR0AABxAH4ACQ==&lt;/byte-array&gt;" />
<option value="&lt;byte-array&gt;rO0ABXNyADZjb20uaXRhbmdjZW50LmlkZWEucGx1Z2luLmFwaS5hY2NvdW50LlByb2plY3RBbmRNb2R1bGUAAAAAAAAAAQIAFVoABmVuYWJsZUwACG1vZHVsZUlkdAASTGphdmEvbGFuZy9TdHJpbmc7TAAGb3RoZXIxcQB+AAFMAAdvdGhlcjEwcQB+AAFMAAdvdGhlcjExcQB+AAFMAAdvdGhlcjEycQB+AAFMAAZvdGhlcjJxAH4AAUwABm90aGVyM3EAfgABTAAGb3RoZXI0cQB+AAFMAAZvdGhlcjVxAH4AAUwABm90aGVyNnEAfgABTAAGb3RoZXI3cQB+AAFMAAZvdGhlcjhxAH4AAUwABm90aGVyOXEAfgABTAAKcGF0aEJlZm9yZXEAfgABTAANcHJvamVjdEZvbGRlcnEAfgABTAAPcHJvamVjdEZvbGRlcklkcQB+AAFMAAlwcm9qZWN0SWRxAH4AAUwAC3Byb2plY3ROYW1lcQB+AAFMAAxzY2hlbWFGb2xkZXJxAH4AAUwACHNjaGVtYUlkcQB+AAF4cAF0AAVjbHVic3BwcHBwcHBwcHBwcHQAAHQACeagueebruW9lXQAATB0AAc1NjAxNzQwdAAQSVRaWC1DbHViLVNlcnZlcnEAfgAFcQB+AAY=&lt;/byte-array&gt;" />
<option value="&lt;byte-array&gt;rO0ABXNyADZjb20uaXRhbmdjZW50LmlkZWEucGx1Z2luLmFwaS5hY2NvdW50LlByb2plY3RBbmRNb2R1bGUAAAAAAAAAAQIAFVoABmVuYWJsZUwACG1vZHVsZUlkdAASTGphdmEvbGFuZy9TdHJpbmc7TAAGb3RoZXIxcQB+AAFMAAdvdGhlcjEwcQB+AAFMAAdvdGhlcjExcQB+AAFMAAdvdGhlcjEycQB+AAFMAAZvdGhlcjJxAH4AAUwABm90aGVyM3EAfgABTAAGb3RoZXI0cQB+AAFMAAZvdGhlcjVxAH4AAUwABm90aGVyNnEAfgABTAAGb3RoZXI3cQB+AAFMAAZvdGhlcjhxAH4AAUwABm90aGVyOXEAfgABTAAKcGF0aEJlZm9yZXEAfgABTAANcHJvamVjdEZvbGRlcnEAfgABTAAPcHJvamVjdEZvbGRlcklkcQB+AAFMAAlwcm9qZWN0SWRxAH4AAUwAC3Byb2plY3ROYW1lcQB+AAFMAAxzY2hlbWFGb2xkZXJxAH4AAUwACHNjaGVtYUlkcQB+AAF4cAF0AApjbHVicy5tYWlucHBwcHBwcHBwcHBwdAAAdAAJ5qC555uu5b2VdAABMHQABzU2MDE3NDB0ABBJVFpYLUNsdWItU2VydmVycQB+AAVxAH4ABg==&lt;/byte-array&gt;" />
</array>
</option>
<option name="treeNodes" value="&lt;byte-array&gt;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&lt;/byte-array&gt;" />
<option name="treeNodesJTree" value="&lt;byte-array&gt;rO0ABXNyACFqYXZheC5zd2luZy50cmVlLkRlZmF1bHRUcmVlTW9kZWynvpEmGsXl2QMAA1oAEmFza3NBbGxvd3NDaGlsZHJlbkwADGxpc3RlbmVyTGlzdHQAJUxqYXZheC9zd2luZy9ldmVudC9FdmVudExpc3RlbmVyTGlzdDtMAARyb290dAAbTGphdmF4L3N3aW5nL3RyZWUvVHJlZU5vZGU7eHAAc3IAI2phdmF4LnN3aW5nLmV2ZW50LkV2ZW50TGlzdGVuZXJMaXN0kUjMLXPfDt4DAAB4cHB4c3IAJ2phdmF4LnN3aW5nLnRyZWUuRGVmYXVsdE11dGFibGVUcmVlTm9kZcRYv/zyqHHgAwADWgAOYWxsb3dzQ2hpbGRyZW5MAAhjaGlsZHJlbnQAEkxqYXZhL3V0aWwvVmVjdG9yO0wABnBhcmVudHQAIkxqYXZheC9zd2luZy90cmVlL011dGFibGVUcmVlTm9kZTt4cAFzcgAQamF2YS51dGlsLlZlY3RvctmXfVuAO68BAwADSQARY2FwYWNpdHlJbmNyZW1lbnRJAAxlbGVtZW50Q291bnRbAAtlbGVtZW50RGF0YXQAE1tMamF2YS9sYW5nL09iamVjdDt4cAAAAAAAAAACdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAACnNxAH4ABgFzcQB+AAoAAAAAAAAAAXVxAH4ADQAAAApzcQB+AAYBcHEAfgAPdXEAfgANAAAAAnQACnVzZXJPYmplY3RzcgAuY29tLml0YW5nY2VudC5pZGVhLnBsdWdpbi5hcGkuYWNjb3VudC5UcmVlTm9kZQAAAAAAAAABAgALTAAHYWxsUGF0aHQAEkxqYXZhL2xhbmcvU3RyaW5nO0wACGNoaWxkcmVudAAPTGphdmEvdXRpbC9NYXA7TAAIZnVsbFBhdGhxAH4AFkwAA2tleXEAfgAWTAAEbmFtZXEAfgAWTAAIcGFyZW50SWRxAH4AFkwACXByb2plY3RJZHEAfgAWTAALcHJvamVjdE5hbWVxAH4AFkwABnRlYW1JZHEAfgAWTAAIdGVhbU5hbWVxAH4AFkwABHR5cGV0ADBMY29tL2l0YW5nY2VudC9pZGVhL3BsdWdpbi9hcGkvYWNjb3VudC9Ob2RlVHlwZTt4cHQAGeS4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&lt;/byte-array&gt;" />
</component>
</project>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

15
.idea/compiler.xml generated
View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Gradle Imported" enabled="true">
<outputRelativeToContentRoot value="true" />
<processorPath useClasspath="false">
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.30/f195ee86e6c896ea47a1d39defbe20eb59cd149d/lombok-1.18.30.jar" />
</processorPath>
<module name="clubs.main" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel target="17" />
</component>
</project>

33
.idea/dataSources.xml generated
View File

@ -1,33 +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="itzx@172.16.127.100" uuid="6d73dcf2-41f4-4403-89d0-be443cbd1b39">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<imported>true</imported>
<remarks>$PROJECT_DIR$/src/main/resources/application.properties</remarks>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://172.16.127.100:3306/itzx</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.resource.type" value="Deployment" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="0@172.16.127.100" uuid="41d00101-c8a0-446a-a28b-8027776414f2">
<driver-ref>redis</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>jdbc.RedisDriver</jdbc-driver>
<jdbc-url>jdbc:redis://172.16.127.100:6379/0</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.resource.type" value="Deployment" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@ -1,15 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,java.lang.foreign.Arena,ofAuto,java.lang.foreign.Arena,global,java.util.concurrent.ConcurrentHashMap,remove" />
</inspection_tool>
<inspection_tool class="IncorrectHttpHeaderInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="customHeaders">
<set>
<option value="Set-Authorization" />
</set>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -1,40 +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="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>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://maven.aliyun.com/repository/gradle-plugin" />
</remote-repository>
<remote-repository>
<option name="id" value="maven4" />
<option name="name" value="maven4" />
<option name="url" value="https://mirrors.cloud.tencent.com/repository/maven/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven2" />
<option name="name" value="maven2" />
<option name="url" value="https://maven.aliyun.com/repository/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven3" />
<option name="name" value="maven3" />
<option name="url" value="https://repo.huaweicloud.com/repository/maven/" />
</remote-repository>
</component>
</project>

22
.idea/misc.xml generated
View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ASMSmaliIdeaPluginConfiguration">
<asm skipDebug="true" skipFrames="true" skipCode="false" expandFrames="false" />
<groovy codeStyle="LEGACY" />
</component>
<component name="EntryPointsManager">
<list size="4">
<item index="0" class="java.lang.String" itemvalue="jakarta.websocket.OnClose" />
<item index="1" class="java.lang.String" itemvalue="jakarta.websocket.OnMessage" />
<item index="2" class="java.lang.String" itemvalue="jakarta.websocket.OnOpen" />
<item index="3" class="java.lang.String" itemvalue="jakarta.websocket.server.ServerEndpoint" />
</list>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</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>

9
.idea/modules.xml generated
View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/clubs.iml" filepath="$PROJECT_DIR$/.idea/modules/clubs.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/clubs.main.iml" filepath="$PROJECT_DIR$/.idea/modules/clubs.main.iml" />
</modules>
</component>
</project>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$/../.." dumb="true">
<excludeFolder url="file://$MODULE_DIR$/../dataSources" />
<excludeFolder url="file://$MODULE_DIR$/../dictionaries" />
<excludeFolder url="file://$MODULE_DIR$/../../run" />
</content>
</component>
</module>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$/../../build/generated/sources/annotationProcessor/java/main">
<sourceFolder url="file://$MODULE_DIR$/../../build/generated/sources/annotationProcessor/java/main" isTestSource="false" generated="true" />
</content>
</component>
</module>

6
.idea/vcs.xml generated
View File

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

87
HELP.md
View File

@ -1,87 +0,0 @@
# Getting Started
### Reference Documentation
For further reference, please consider the following sections:
* [Official Gradle documentation](https://docs.gradle.org)
* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.2.4/gradle-plugin/reference/html/)
* [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.2.4/gradle-plugin/reference/html/#build-image)
* [GraalVM Native Image Support](https://docs.spring.io/spring-boot/docs/3.2.4/reference/html/native-image.html#native-image)
* [Spring Boot Actuator](https://docs.spring.io/spring-boot/docs/3.2.4/reference/htmlsingle/index.html#actuator)
* [Spring Boot DevTools](https://docs.spring.io/spring-boot/docs/3.2.4/reference/htmlsingle/index.html#using.devtools)
* [MyBatis Framework](https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/)
* [Spring REST Docs](https://docs.spring.io/spring-restdocs/docs/current/reference/htmlsingle/)
* [Spring Shell](https://spring.io/projects/spring-shell)
* [Spring Web](https://docs.spring.io/spring-boot/docs/3.2.4/reference/htmlsingle/index.html#web)
* [WebSocket](https://docs.spring.io/spring-boot/docs/3.2.4/reference/htmlsingle/index.html#messaging.websockets)
### Guides
The following guides illustrate how to use some features concretely:
* [Building a RESTful Web Service with Spring Boot Actuator](https://spring.io/guides/gs/actuator-service/)
* [MyBatis Quick Start](https://github.com/mybatis/spring-boot-starter/wiki/Quick-Start)
* [Accessing data with MySQL](https://spring.io/guides/gs/accessing-data-mysql/)
* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/)
* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/)
* [Building REST services with Spring](https://spring.io/guides/tutorials/rest/)
* [Using WebSocket to build an interactive web application](https://spring.io/guides/gs/messaging-stomp-websocket/)
### Additional Links
These additional references should also help you:
* [Gradle Build Scans insights for your project's build](https://scans.gradle.com#gradle)
* [Configure AOT settings in Build Plugin](https://docs.spring.io/spring-boot/docs/3.2.4/gradle-plugin/reference/htmlsingle/#aot)
## GraalVM Native Support
This project has been configured to let you generate either a lightweight container or a native executable.
It is also possible to run your tests in a native image.
### Lightweight Container with Cloud Native Buildpacks
If you're already familiar with Spring Boot container images support, this is the easiest way to get started.
Docker should be installed and configured on your machine prior to creating the image.
To create the image, run the following goal:
```
$ ./gradlew bootBuildImage
```
Then, you can run the app like any other container:
```
$ docker run --rm -p 8080:8080 clubs:0.0.1-SNAPSHOT
```
### Executable with Native Build Tools
Use this option if you want to explore more options such as running your tests in a native image.
The GraalVM `native-image` compiler should be installed and configured on your machine.
NOTE: GraalVM 22.3+ is required.
To create the executable, run the following goal:
```
$ ./gradlew nativeCompile
```
Then, you can run the app as follows:
```
$ build/native/nativeCompile/clubs
```
You can also run your existing tests suite in a native image.
This is an efficient way to validate the compatibility of your application.
To run your existing tests in a native image, run the following goal:
```
$ ./gradlew nativeTest
```

View File

@ -1,9 +0,0 @@
MIT License
Copyright (c) 2024 ITZX
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -3,35 +3,6 @@
A clubs home for Hangzhou Electron & Information Vocational School A clubs home for Hangzhou Electron & Information Vocational School
building by springboot3.0 with mysql db building by springboot3.0 with mysql db
--- ---
<h1><center><font color="red"> ⚠⚠⚠⚠ <font color="yellow">🚮 D.E.P.R.E.C.A.T.E.D </font>⚠⚠⚠⚠ </font></center></h1>
---
## Usage ## Usage
### 1.Create user table ### Add QQ(3357223099(wzp))/QQ (3315932219(Litrix)) and tell him you want to deployment this site
```mysql
create table user
(
id int comment '用户ID自增',
name varchar(20) not null comment '用户名',
password char(40) not null comment '密码使用SHA1',
auth int default 0 not null comment '用户对应权限组ID',
avatar char(40) null comment '用户头像sha1',
create_time datetime default now() not null comment '用户创建时间',
update_time datetime default now() not null on update now() comment '用户数据更新时间'
)
comment '用户表';
create unique index user_id_index
on user (id)
comment '用户表主键';
alter table user
add constraint user_pk
primary key (id) comment '用户表主键';
alter table user
modify id int auto_increment comment '用户ID自增';
```

View File

@ -1,16 +1,16 @@
plugins { plugins {
java java
id("org.springframework.boot") version "3.2.4" id("org.springframework.boot") version "3.4.0"
id("io.spring.dependency-management") version "1.1.4" id("io.spring.dependency-management") version "1.1.6"
id("org.asciidoctor.jvm.convert") version "3.3.2"
id("org.springdoc.openapi-gradle-plugin") version "1.8.0"
} }
group = "org.mmga" group = "org.blue"
version = "0.0.1-SNAPSHOT" version = "0.0.1-SNAPSHOT"
java { java {
sourceCompatibility = JavaVersion.VERSION_17 toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
} }
configurations { configurations {
@ -20,59 +20,29 @@ configurations {
} }
repositories { repositories {
maven { url = uri("https://maven.aliyun.com/repository/public/") }
maven { url = uri("https://repo.huaweicloud.com/repository/maven/") }
maven { url = uri("https://mirrors.cloud.tencent.com/repository/maven/") }
maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
mavenCentral() mavenCentral()
maven {
url = uri("https://wzpmc.cn:90/repository/maven-public/")
}
} }
extra["snippetsDir"] = file("build/generated-snippets")
extra["springShellVersion"] = "3.2.3"
dependencies { dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-web") {
exclude("com.fasterxml.jackson.core", "jackson-core")
}
implementation("org.springframework.boot:spring-boot-starter-websocket") implementation("org.springframework.boot:spring-boot-starter-websocket")
implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3") implementation("org.mmga:make-minecraft-great-again-spring-boot-starter:0.0.5")
implementation("org.springframework.shell:spring-shell-starter") implementation("com.mybatis-flex:mybatis-flex-spring-boot-starter:1.10.2")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-api:2.5.0") annotationProcessor("com.mybatis-flex:mybatis-flex-processor:1.10.2")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0")
// https://mvnrepository.com/artifact/commons-codec/commons-codec // https://mvnrepository.com/artifact/commons-codec/commons-codec
implementation("commons-codec:commons-codec:1.16.1") implementation("commons-codec:commons-codec:1.16.1")
// https://mvnrepository.com/artifact/com.auth0/java-jwt implementation("org.springframework.boot:spring-boot-starter-data-redis:3.4.0")
// https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2
implementation("com.alibaba.fastjson2:fastjson2:2.0.48")
// https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2-extension-spring6
implementation("com.alibaba.fastjson2:fastjson2-extension-spring6:2.0.48")
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis
implementation("org.springframework.boot:spring-boot-starter-data-redis:3.2.4")
implementation("com.auth0:java-jwt:4.4.0")
compileOnly("org.projectlombok:lombok") compileOnly("org.projectlombok:lombok")
developmentOnly("org.springframework.boot:spring-boot-devtools")
runtimeOnly("com.mysql:mysql-connector-j") runtimeOnly("com.mysql:mysql-connector-j")
annotationProcessor("org.projectlombok:lombok") annotationProcessor("org.projectlombok:lombok")
testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.3") testImplementation("org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.4")
} testRuntimeOnly("org.junit.platform:junit-platform-launcher")
dependencyManagement {
imports {
mavenBom("org.springframework.shell:spring-shell-dependencies:${property("springShellVersion")}")
}
} }
tasks.withType<Test> { tasks.withType<Test> {
useJUnitPlatform() useJUnitPlatform()
} }
tasks.test {
outputs.dir(project.extra["snippetsDir"]!!)
}
tasks.asciidoctor {
inputs.dir(project.extra["snippetsDir"]!!)
dependsOn(tasks.test)
}

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

7
gradlew vendored
View File

@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum

2
gradlew.bat vendored
View File

@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################

View File

@ -1 +1 @@
rootProject.name = "clubs" rootProject.name = "club"

View File

@ -0,0 +1,24 @@
package org.blue.club;
import com.mybatisflex.core.audit.AuditManager;
import com.mybatisflex.core.audit.ConsoleMessageCollector;
import com.mybatisflex.core.audit.MessageCollector;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
@SpringBootApplication
@EnableWebSocket
@EnableScheduling
public class ClubApplication {
public static void main(String[] args) {
//开启审计功能
AuditManager.setAuditEnable(true);
MessageCollector collector = new ConsoleMessageCollector();
AuditManager.setMessageCollector(collector);
SpringApplication.run(ClubApplication.class, args);
}
}

View File

@ -0,0 +1,22 @@
package org.blue.club.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {
PermissionDescription[] permissions() default {};
LogicType permissionType() default LogicType.ANY;
AuthDescription[] auths() default {};
LogicType authType() default LogicType.ANY;
enum LogicType {
ANY, ALL
}
}

View File

@ -0,0 +1,14 @@
package org.blue.club.annotation;
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.ANNOTATION_TYPE)
public @interface AuthDescription {
DescriptionType type() default DescriptionType.NORMAL;
long value();
}

View File

@ -0,0 +1,6 @@
package org.blue.club.annotation;
public enum DescriptionType {
NORMAL,
CLUB
}

View File

@ -0,0 +1,14 @@
package org.blue.club.annotation;
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.ANNOTATION_TYPE)
public @interface PermissionDescription {
DescriptionType type() default DescriptionType.NORMAL;
long value();
}

View File

@ -0,0 +1,18 @@
package org.blue.club.configuration;
import lombok.RequiredArgsConstructor;
import org.blue.club.annotation.Auth;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.annotation.Annotation;
@Configuration
@RequiredArgsConstructor
public class AuthConfiguration {
@Bean
public Class<? extends Annotation> authAnnotation() {
return Auth.class;
}
}

View File

@ -0,0 +1,66 @@
package org.blue.club.configuration;
import lombok.RequiredArgsConstructor;
import org.blue.club.annotation.Auth;
import org.blue.club.annotation.AuthDescription;
import org.blue.club.annotation.PermissionDescription;
import org.blue.club.dao.AuthDao;
import org.blue.club.dao.UserDao;
import org.blue.club.entities.dto.user.User;
import org.blue.club.entities.vo.data.UserVo;
import org.mmga.spring.boot.starter.componet.AuthorizationHandler;
import org.mmga.spring.boot.starter.componet.JwtUtils;
import org.mmga.spring.boot.starter.entities.Result;
import org.mmga.spring.boot.starter.exception.AuthorizationException;
import org.mmga.spring.boot.starter.utils.VoUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Stream;
@Component
@RequiredArgsConstructor
public class CustomAuthorizationHandler implements AuthorizationHandler<User> {
private final JwtUtils jwtUtils;
private final UserDao userDao;
private final VoUtils voUtils;
private final AuthDao authDao;
private User guestUser;
@Override
public Optional<User> auth(String token, Annotation ann) {
if (ann instanceof Auth auth) {
User user = guestUser;
if (guestUser == null) {
user = new User();
user.setId(-1L);
user.setName("游客");
user.setAuth(authDao.selectOneWithRelationsById(1));
guestUser = user;
}
if (token != null) {
Optional<Long> i = jwtUtils.verifyToken(token);
if (i.isEmpty()) throw new AuthorizationException(Result.failed(HttpStatus.UNAUTHORIZED, "token错误"));
Long userId = i.get();
UserVo userVo = userDao.selectOneWithRelationsById(userId);
if (userVo == null)
throw new AuthorizationException(Result.failed(HttpStatus.UNAUTHORIZED, "用户不存在!"));
user = voUtils.vo2DtoSafe(userVo, User.class);
}
AuthDescription[] auths = auth.auths();
Stream<AuthDescription> authStream = Arrays.stream(auths);
boolean isAuthAccept = auths.length == 0 || (auth.authType().equals(Auth.LogicType.ANY) ? authStream.anyMatch(user::hasAuth) : authStream.allMatch(user::hasAuth));
if (!isAuthAccept) throw new AuthorizationException(Result.failed(HttpStatus.UNAUTHORIZED, "权限不足!"));
PermissionDescription[] permissions = auth.permissions();
Stream<PermissionDescription> permissionStream = Arrays.stream(permissions);
boolean isPermissionAccept = permissions.length == 0 || (auth.permissionType().equals(Auth.LogicType.ANY) ? permissionStream.anyMatch(user::hasPermission) : permissionStream.allMatch(user::hasPermission));
if (!isPermissionAccept)
throw new AuthorizationException(Result.failed(HttpStatus.UNAUTHORIZED, "权限不足!"));
return Optional.of(user);
}
return Optional.empty();
}
}

View File

@ -0,0 +1,20 @@
package org.blue.club.configuration;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class CustomDocsConfiguration {
@Bean
@Primary
public OpenAPI myApi(@Autowired OpenAPI api) {
api.addServersItem(new Server().url("https://wzpmc.cn:18080/").description("外网开放API"));
api.addServersItem(new Server().url("http://172.16.114.84:58082/").description("开发用API"));
api.addServersItem(new Server().url("http://127.0.0.1:58082/").description("调试用API"));
return api;
}
}

View File

@ -0,0 +1,27 @@
package org.blue.club.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.File;
import java.io.IOException;
@Configuration
public class DirConfiguration {
@Bean
public File tmpFolder() throws IOException {
return createDefaultFolder("tmp");
}
@Bean
public File avatarFolder() throws IOException {
return createDefaultFolder("avatar");
}
private File createDefaultFolder(String name) throws IOException {
File folderFile = new File(name);
if (folderFile.exists()) return folderFile;
if (!folderFile.mkdirs()) throw new IOException("无法创建文件夹" + name);
return folderFile;
}
}

View File

@ -1,4 +1,4 @@
package org.mmga.clubs.configuration; package org.blue.club.configuration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;

View File

@ -0,0 +1,33 @@
package org.blue.club.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.blue.club.annotation.Auth;
import org.blue.club.services.AvatarServices;
import org.mmga.spring.boot.starter.entities.Result;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/api/avatar")
@Tag(name = "头像相关接口")
@RequiredArgsConstructor
public class AvatarController {
private final AvatarServices avatarServices;
@GetMapping("/{sha1}")
@Operation(description = "获取头像文件")
public void getAvatar(HttpServletResponse response, @Schema(description = "头像文件SHA1值") @PathVariable("sha1") String sha1) {
avatarServices.getAvatar(response, sha1);
}
@PostMapping("/upload")
@Operation(description = "上传头像")
@Auth
public Result<String> changeAvatar(MultipartFile file) {
return avatarServices.changeAvatar(file);
}
}

View File

@ -1,4 +1,4 @@
package org.mmga.clubs.controller; package org.blue.club.controller;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONException; import com.alibaba.fastjson2.JSONException;
@ -13,13 +13,14 @@ import jakarta.websocket.server.ServerEndpoint;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.mmga.clubs.entities.chess.Room; import org.blue.club.dao.UserDao;
import org.mmga.clubs.entities.chess.packet.*; import org.blue.club.entities.dto.chess.Room;
import org.mmga.clubs.entities.chess.packet.request.*; import org.blue.club.entities.dto.chess.packet.*;
import org.mmga.clubs.entities.user.User; import org.blue.club.entities.dto.chess.packet.request.*;
import org.mmga.clubs.service.UserService; import org.blue.club.entities.dto.user.User;
import org.mmga.clubs.utils.JwtUtils; import org.blue.club.utils.WebSocketUtils;
import org.mmga.clubs.utils.WebSocketUtils; import org.mmga.spring.boot.starter.componet.JwtUtils;
import org.mmga.spring.boot.starter.utils.VoUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -43,26 +44,79 @@ public class ChessController {
private static final ConcurrentHashMap<UUID, Room> rooms = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<UUID, Room> rooms = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<Session, Boolean> pingPong = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<Session, Boolean> pingPong = new ConcurrentHashMap<>();
private static JwtUtils jwtUtils; private static JwtUtils jwtUtils;
private static UserService userService; private static VoUtils voUtils;
private static UserDao userDao;
private final Queue<String> messageQueue = new ConcurrentLinkedQueue<>();
private boolean isLogin = false;
@Nullable
private static Room getRoomByPlayer(String id) {
for (Room value : rooms.values()) {
if (value.isIn(id)) return value;
}
return null;
}
public static void staticOnClose(Session session) {
String sessionId = session.getId();
Room roomByPlayer = getRoomByPlayer(sessionId);
if (roomByPlayer != null) {
roomByPlayer.leave(session);
roomByPlayer.broadcast(roomByPlayer.getRoomInfo(users));
}
log.info("session closed {}", sessionId);
sessions.remove(sessionId);
users.remove(sessionId);
pingPong.remove(session);
}
@SneakyThrows
@Scheduled(fixedDelay = 10000)
public static void connectionManager() {
for (Session value : sessions.values()) {
if (!pingPong.getOrDefault(value, true)) {
log.info("closed by connectionManager");
if (value.isOpen()) {
value.close(new CloseReason(CloseReason.CloseCodes.CLOSED_ABNORMALLY, "链接延迟过高"));
continue;
}
staticOnClose(value);
continue;
}
pingPong.put(value, false);
log.info("send ping to session {}", value.getId());
value.getAsyncRemote().sendPing(Unpooled.buffer().writeLong(System.currentTimeMillis()).nioBuffer());
}
Instant instant = new Date().toInstant();
Instant before10Min = instant.plus(-10, ChronoUnit.MINUTES);
rooms.entrySet().stream()
.filter(e -> e.getValue().getState().equals(Room.RoomState.CREATED))
.filter(e -> e.getValue().getLastStateChange().toInstant().isBefore(before10Min))
.map(Map.Entry::getKey)
.forEach(rooms::remove);
rooms.values().forEach(Room::tryClean);
}
@Autowired
public void setVoUtils(VoUtils voUtils) {
ChessController.voUtils = voUtils;
}
@Autowired
public void setUserDao(UserDao userDao) {
ChessController.userDao = userDao;
}
@Autowired @Autowired
public void setJwtUtils(JwtUtils jwtUtils) { public void setJwtUtils(JwtUtils jwtUtils) {
ChessController.jwtUtils = jwtUtils; ChessController.jwtUtils = jwtUtils;
} }
@Autowired
public void setUserService(UserService userService) {
ChessController.userService = userService;
}
private final Queue<String> messageQueue = new ConcurrentLinkedQueue<>();
private boolean isLogin = false;
@SneakyThrows @SneakyThrows
@OnOpen @OnOpen
public void onOpen(Session session, @PathParam("token") String token) { public void onOpen(Session session, @PathParam("token") String token) {
isLogin = false; isLogin = false;
Optional<Integer> i = jwtUtils.verifyTokenSafe(token); Optional<Long> i = jwtUtils.verifyToken(token);
if (i.isEmpty()) { if (i.isEmpty()) {
WebSocketUtils.sendPacket(new ErrorPacket("token验证失败"), session); WebSocketUtils.sendPacket(new ErrorPacket("token验证失败"), session);
log.info("closed by token verifier"); log.info("closed by token verifier");
@ -71,10 +125,10 @@ public class ChessController {
return; return;
} }
String sessionId = session.getId(); String sessionId = session.getId();
User user = userService.getUserById(i.get()); User user = voUtils.vo2DtoSafe(userDao.selectOneWithRelationsById(i.get()), User.class);
WebSocketUtils.sendPacket(new UserInfoPacket(user), session); WebSocketUtils.sendPacket(new UserInfoPacket(user), session);
String existsUser = null; String existsUser = null;
int id = user.getId(); long id = user.getId();
for (Map.Entry<String, User> entry : users.entrySet()) { for (Map.Entry<String, User> entry : users.entrySet()) {
User value = entry.getValue(); User value = entry.getValue();
if (value.getId() == id) { if (value.getId() == id) {
@ -102,7 +156,6 @@ public class ChessController {
} }
} }
@OnMessage @OnMessage
public void onPong(PongMessage pongMessage, Session session) { public void onPong(PongMessage pongMessage, Session session) {
ByteBuffer applicationData = pongMessage.getApplicationData(); ByteBuffer applicationData = pongMessage.getApplicationData();
@ -138,7 +191,7 @@ public class ChessController {
public Object handleMessage(JSONObject object, String packetName, Session session) { public Object handleMessage(JSONObject object, String packetName, Session session) {
if (packetName == null) return new ErrorPacket("错误的请求类型"); if (packetName == null) return new ErrorPacket("错误的请求类型");
try { try {
Class<?> packetType = Class.forName("org.mmga.clubs.entities.chess.packet.request." + packetName + "Request"); Class<?> packetType = Class.forName("org.blue.club.entities.dto.chess.packet.request." + packetName + "Request");
JSONObject payloadJson = object.getJSONObject("payload"); JSONObject payloadJson = object.getJSONObject("payload");
if (payloadJson == null) { if (payloadJson == null) {
Field[] declaredFields = packetType.getDeclaredFields(); Field[] declaredFields = packetType.getDeclaredFields();
@ -212,57 +265,8 @@ public class ChessController {
return null; return null;
} }
@Nullable
private static Room getRoomByPlayer(String id) {
for (Room value : rooms.values()) {
if (value.isIn(id)) return value;
}
return null;
}
public static void staticOnClose(Session session) {
String sessionId = session.getId();
Room roomByPlayer = getRoomByPlayer(sessionId);
if (roomByPlayer != null) {
roomByPlayer.leave(session);
roomByPlayer.broadcast(roomByPlayer.getRoomInfo(users));
}
log.info("session closed {}", sessionId);
sessions.remove(sessionId);
users.remove(sessionId);
pingPong.remove(session);
}
@OnClose @OnClose
public void onClose(Session session) { public void onClose(Session session) {
staticOnClose(session); staticOnClose(session);
} }
@SneakyThrows
@Scheduled(fixedDelay = 10000)
public static void connectionManager() {
for (Session value : sessions.values()) {
if (!pingPong.getOrDefault(value, true)) {
log.info("closed by connectionManager");
if (value.isOpen()) {
value.close(new CloseReason(CloseReason.CloseCodes.CLOSED_ABNORMALLY, "链接延迟过高"));
continue;
}
staticOnClose(value);
continue;
}
pingPong.put(value, false);
log.info("send ping to session {}", value.getId());
value.getAsyncRemote().sendPing(Unpooled.buffer().writeLong(System.currentTimeMillis()).nioBuffer());
}
Instant instant = new Date().toInstant();
Instant before10Min = instant.plus(-10, ChronoUnit.MINUTES);
rooms.entrySet().stream()
.filter(e -> e.getValue().getState().equals(Room.RoomState.CREATED))
.filter(e -> e.getValue().getLastStateChange().toInstant().isBefore(before10Min))
.map(Map.Entry::getKey)
.forEach(rooms::remove);
rooms.values().forEach(Room::tryClean);
}
} }

View File

@ -0,0 +1,86 @@
package org.blue.club.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.blue.club.annotation.Auth;
import org.blue.club.annotation.DescriptionType;
import org.blue.club.annotation.PermissionDescription;
import org.blue.club.entities.dto.club.req.*;
import org.blue.club.entities.dto.user.User;
import org.blue.club.entities.vo.data.ClubVo;
import org.blue.club.services.ClubServices;
import org.mmga.spring.boot.starter.entities.PagerData;
import org.mmga.spring.boot.starter.entities.Result;
import org.springframework.web.bind.annotation.*;
@RestController
@Tag(name = "社团相关接口")
@RequestMapping("/api/club")
@RequiredArgsConstructor
public class ClubController {
private final ClubServices clubServices;
@GetMapping("/")
@Operation(description = "分页获取社团")
public Result<PagerData<ClubVo>> getClubs(@Schema(description = "分页数据第几页") @RequestParam Integer page, @Schema(description = "分页数据每页数据量") @RequestParam Integer num) {
return clubServices.getClubs(page, num);
}
@Operation(description = "创建社团")
@Auth(permissions = {@PermissionDescription(3)})
@PutMapping("/create")
public Result<ClubVo> createClub(@RequestBody CreateClubRequest request) {
return clubServices.createClub(request);
}
@Operation(description = "向社团添加用户")
@PutMapping("/user/add")
public Result<Boolean> clubAddUser(@Auth(permissions = {@PermissionDescription(3), @PermissionDescription(value = 2, type = DescriptionType.CLUB)}) User user, @RequestBody ClubAddUserRequest request) {
return clubServices.clubAddUser(user, request);
}
@Operation(description = "分页列出社团的所有用户")
@GetMapping("/user/list")
public Result<PagerData<User>> clubGetUsers(@Schema(description = "分页数据第几页") @RequestParam Integer page, @Schema(description = "分页数据每页数据量") @RequestParam Integer num, @Schema(description = "社团ID") @RequestParam Long clubId) {
return clubServices.clubGetUsers(page, num, clubId);
}
@Operation(description = "从社团中删除用户")
@DeleteMapping("/user/remove")
public Result<Boolean> clubRemoveUser(@Auth(permissions = {@PermissionDescription(3), @PermissionDescription(value = 2, type = DescriptionType.CLUB)}) User user, @Schema(description = "社团ID") @RequestParam long clubId, @Schema(description = "需要删除的用户ID") @RequestParam long userId) {
return clubServices.clubRemoveUser(user, clubId, userId);
}
@Operation(description = "修改社团用户权限")
@PutMapping("/user/auth")
public Result<Boolean> clubChangeUserAuth(@Auth(permissions = {@PermissionDescription(3), @PermissionDescription(value = 2, type = DescriptionType.CLUB)}) User user, @RequestBody ClubChangeUserAuthRequest request) {
return clubServices.clubChangeUserAuth(user, request);
}
@PutMapping("/avatar")
@Operation(description = "修改社团头像")
public Result<Boolean> changeAvatar(@RequestParam("code") @Schema(description = "修改头像操作码,可以通过将图片文件上传至([POST] /api/avatar/upload)接口获取") String avatarOperationCode, @Schema(description = "被修改的社团ID") @RequestParam long clubId, @Auth(permissions = {@PermissionDescription(3), @PermissionDescription(value = 1, type = DescriptionType.CLUB)}) User user) {
return clubServices.changeAvatar(avatarOperationCode, clubId, user);
}
@PutMapping("/name")
@Operation(description = "修改社团名称")
@Auth(permissions = {@PermissionDescription(3)})
public Result<Boolean> changeName(@RequestBody ChangeClubNameRequest request) {
return clubServices.changeName(request);
}
@PutMapping("/commit")
@Operation(description = "修改社团名称")
public Result<Boolean> changeCommit(@RequestBody ChangeClubCommitRequest request, @Auth(permissions = {@PermissionDescription(3), @PermissionDescription(value = 1, type = DescriptionType.CLUB)}) User user) {
return clubServices.changeCommit(request, user);
}
@GetMapping("/{id}")
@Operation(description = "获取社团")
public Result<ClubVo> getClub(@Schema(description = "社团ID") @PathVariable("id") long id) {
return clubServices.getClub(id);
}
}

View File

@ -0,0 +1,16 @@
package org.blue.club.controller;
import org.blue.club.exceptions.ServerException;
import org.mmga.spring.boot.starter.entities.Result;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class ServerExceptionHandler {
@ExceptionHandler({ServerException.class})
public ResponseEntity<Result<Void>> handleAuthorizationException(ServerException e) {
Result<Void> result = e.getResult();
return ResponseEntity.ok(result);
}
}

View File

@ -0,0 +1,92 @@
package org.blue.club.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.blue.club.annotation.Auth;
import org.blue.club.annotation.PermissionDescription;
import org.blue.club.entities.dto.user.User;
import org.blue.club.entities.dto.user.req.*;
import org.blue.club.entities.dto.user.resp.VerifyCodeResponse;
import org.blue.club.services.UserServices;
import org.mmga.spring.boot.starter.annotation.AuthMapping;
import org.mmga.spring.boot.starter.entities.PagerData;
import org.mmga.spring.boot.starter.entities.Result;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
@Tag(name = "用户相关接口")
public class UserController {
private final UserServices userServices;
@PostMapping("/login")
@AuthMapping
@Operation(description = "用户登录", responses = {@ApiResponse(description = "返回是否登录成功", responseCode = "200")})
public Result<User> login(@RequestBody LoginRequest user) {
return userServices.login(user);
}
@AuthMapping
@PutMapping("/create")
@Operation(description = "创建用户", responses = {@ApiResponse(description = "返回创建后的用户")})
public Result<User> createUser(@RequestBody RegisterRequest user) {
return userServices.createUser(user);
}
@GetMapping("/info")
@Operation(description = "获取用户信息")
public Result<User> getUserInfo(@Auth User user) {
return Result.success(user);
}
@GetMapping("/all")
@Operation(description = "获取所有用户信息(分页)")
@Auth(permissions = {@PermissionDescription(3), @PermissionDescription(4)})
public Result<PagerData<User>> getAllUserInfo(@RequestParam("num") int num, @RequestParam("page") int page, @RequestParam(value = "onlyNoClub", defaultValue = "false") boolean onlyNoClub) {
return userServices.getAllUserInfo(num, page, onlyNoClub);
}
@PutMapping("/rename")
@Operation(description = "修改用户名")
@Auth
public Result<Boolean> changeUsername(@RequestBody UserRenameRequest renameVo, @Auth User user) {
return userServices.changeUsername(renameVo, user);
}
@PutMapping("/password")
@Operation(description = "修改密码")
public Result<Boolean> changePassword(@RequestBody UserChangePasswordRequest changePasswordVo, @Auth User user) {
return userServices.changePassword(changePasswordVo, user);
}
@PutMapping("/auth")
@Operation(description = "修改用户权限组")
@Auth(permissions = {@PermissionDescription(3), @PermissionDescription(4)})
public Result<Boolean> changeAuth(@RequestBody UserChangeAuthRequest userChangeAuthVo) {
return userServices.changeAuth(userChangeAuthVo);
}
@PutMapping("/avatar")
@Operation(description = "修改用户头像")
public Result<Boolean> changeAvatar(@RequestParam("code") @Schema(description = "修改头像操作码,可以通过将图片文件上传至([POST] /api/avatar/upload)接口获取") String avatarOperationCode, @Schema(description = "被修改的用户ID", requiredMode = Schema.RequiredMode.NOT_REQUIRED) @RequestParam(defaultValue = "-1") long userId, @Auth User user) {
return userServices.changeAvatar(avatarOperationCode, userId, user);
}
@GetMapping("/verify")
@Operation(description = "获取验证码")
public Result<VerifyCodeResponse> generatorVerifyCode() {
return userServices.getVerifyCode();
}
@GetMapping("/info/{id}")
@Operation(description = "获取简略用户信息")
public Result<User> getSimpleInfo(@PathVariable("id") int userId) {
return userServices.getUserInfo(userId);
}
}

View File

@ -0,0 +1,9 @@
package org.blue.club.dao;
import com.mybatisflex.core.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.blue.club.entities.vo.data.AuthVo;
@Mapper
public interface AuthDao extends BaseMapper<AuthVo> {
}

View File

@ -0,0 +1,9 @@
package org.blue.club.dao;
import com.mybatisflex.core.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.blue.club.entities.vo.data.ClubVo;
@Mapper
public interface ClubDao extends BaseMapper<ClubVo> {
}

View File

@ -0,0 +1,9 @@
package org.blue.club.dao;
import com.mybatisflex.core.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.blue.club.entities.vo.data.ClubUserAuthVo;
@Mapper
public interface ClubUserAuthDao extends BaseMapper<ClubUserAuthVo> {
}

View File

@ -0,0 +1,9 @@
package org.blue.club.dao;
import com.mybatisflex.core.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.blue.club.entities.vo.data.UserVo;
@Mapper
public interface UserDao extends BaseMapper<UserVo> {
}

View File

@ -0,0 +1,9 @@
package org.blue.club.dao.redis;
import org.blue.club.entities.vo.data.AvatarTmpVo;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface AvatarOperationDao extends CrudRepository<AvatarTmpVo, String> {
}

View File

@ -0,0 +1,9 @@
package org.blue.club.dao.redis;
import org.blue.club.entities.vo.VerifyVo;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface VerifyDao extends CrudRepository<VerifyVo, String> {
}

View File

@ -1,4 +1,4 @@
package org.mmga.clubs.entities.chess; package org.blue.club.entities.dto.chess;
import com.alibaba.fastjson2.annotation.JSONField; import com.alibaba.fastjson2.annotation.JSONField;
@ -7,12 +7,12 @@ import lombok.AccessLevel;
import lombok.Data; import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.mmga.clubs.entities.chess.packet.PlayerJoinRoomPacket; import org.blue.club.entities.dto.chess.packet.PlayerJoinRoomPacket;
import org.mmga.clubs.entities.chess.packet.PlayerLeavePacket; import org.blue.club.entities.dto.chess.packet.PlayerLeavePacket;
import org.mmga.clubs.entities.chess.packet.PlayerSideAllocationPacket; import org.blue.club.entities.dto.chess.packet.PlayerSideAllocationPacket;
import org.mmga.clubs.entities.chess.packet.RoomInfoPacket; import org.blue.club.entities.dto.chess.packet.RoomInfoPacket;
import org.mmga.clubs.entities.user.User; import org.blue.club.entities.dto.user.User;
import org.mmga.clubs.utils.WebSocketUtils; import org.blue.club.utils.WebSocketUtils;
import java.util.*; import java.util.*;

View File

@ -1,4 +1,4 @@
package org.mmga.clubs.entities.chess.packet; package org.blue.club.entities.dto.chess.packet;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import lombok.Data; import lombok.Data;

View File

@ -0,0 +1,4 @@
package org.blue.club.entities.dto.chess.packet;
public record ErrorPacket(String reason) {
}

View File

@ -0,0 +1,6 @@
package org.blue.club.entities.dto.chess.packet;
import org.blue.club.entities.dto.chess.Room;
public record HasPlayerWinPacket(Room.WinInfo winInfo) {
}

View File

@ -0,0 +1,7 @@
package org.blue.club.entities.dto.chess.packet;
import org.blue.club.entities.dto.chess.Room;
import org.blue.club.entities.dto.user.User;
public record PlayerJoinRoomPacket(User user, Room room) {
}

View File

@ -1,4 +1,4 @@
package org.mmga.clubs.entities.chess.packet; package org.blue.club.entities.dto.chess.packet;
public record PlayerLeavePacket(String player) { public record PlayerLeavePacket(String player) {
} }

View File

@ -0,0 +1,4 @@
package org.blue.club.entities.dto.chess.packet;
public record PlayerLosePacket() {
}

View File

@ -1,4 +1,4 @@
package org.mmga.clubs.entities.chess.packet; package org.blue.club.entities.dto.chess.packet;
public record PlayerSideAllocationPacket(boolean isWhite) { public record PlayerSideAllocationPacket(boolean isWhite) {
} }

View File

@ -0,0 +1,4 @@
package org.blue.club.entities.dto.chess.packet;
public record PlayerWinPacket() {
}

View File

@ -1,4 +1,4 @@
package org.mmga.clubs.entities.chess.packet; package org.blue.club.entities.dto.chess.packet;
import java.util.UUID; import java.util.UUID;

View File

@ -1,7 +1,7 @@
package org.mmga.clubs.entities.chess.packet; package org.blue.club.entities.dto.chess.packet;
import org.mmga.clubs.entities.chess.Room; import org.blue.club.entities.dto.chess.Room;
import org.mmga.clubs.entities.user.User; import org.blue.club.entities.dto.user.User;
import java.util.UUID; import java.util.UUID;

View File

@ -0,0 +1,8 @@
package org.blue.club.entities.dto.chess.packet;
import org.blue.club.entities.dto.chess.Room;
import java.util.Collection;
public record RoomListPacket(Collection<Room> rooms) {
}

View File

@ -0,0 +1,6 @@
package org.blue.club.entities.dto.chess.packet;
import org.blue.club.entities.dto.user.User;
public record UserInfoPacket(User user) {
}

View File

@ -0,0 +1,4 @@
package org.blue.club.entities.dto.chess.packet.request;
public record CreateRoomRequest() {
}

View File

@ -0,0 +1,4 @@
package org.blue.club.entities.dto.chess.packet.request;
public record PlaceChessPieceRequest(int x, int y) {
}

View File

@ -1,4 +1,4 @@
package org.mmga.clubs.entities.chess.packet.request; package org.blue.club.entities.dto.chess.packet.request;
import java.util.UUID; import java.util.UUID;

View File

@ -0,0 +1,4 @@
package org.blue.club.entities.dto.chess.packet.request;
public record ResetRoomRequest() {
}

View File

@ -0,0 +1,4 @@
package org.blue.club.entities.dto.chess.packet.request;
public record RoomListRequest() {
}

View File

@ -0,0 +1,8 @@
package org.blue.club.entities.dto.club.req;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(name = "修改社团简介请求")
public record ChangeClubCommitRequest(@Schema(description = "社团ID") Long clubId,
@Schema(description = "新社团简介") String commit) {
}

View File

@ -0,0 +1,8 @@
package org.blue.club.entities.dto.club.req;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(name = "修改社团名称请求")
public record ChangeClubNameRequest(@Schema(description = "社团ID") Long clubId,
@Schema(description = "新社团名称") String name) {
}

View File

@ -0,0 +1,7 @@
package org.blue.club.entities.dto.club.req;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(name = "社团添加用户请求体")
public record ClubAddUserRequest(@Schema(description = "社团ID") long clubId, @Schema(description = "用户ID") long userId, @Schema(description = "用户在社团的权限组ID") long clubAuthId) {
}

View File

@ -0,0 +1,7 @@
package org.blue.club.entities.dto.club.req;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(name = "修改社团用户权限组请求体")
public record ClubChangeUserAuthRequest(@Schema(description = "修改的用户ID") Long userId,@Schema(description = "社团ID") Long clubId,@Schema(description = "新的AuthID") Long newAuthId) {
}

View File

@ -0,0 +1,7 @@
package org.blue.club.entities.dto.club.req;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(name = "创建社团请求体")
public record CreateClubRequest(@Schema(description = "社团名称") String name) {
}

View File

@ -0,0 +1,51 @@
package org.blue.club.entities.dto.user;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.RelationManyToOne;
import jakarta.annotation.Nullable;
import lombok.Data;
import org.blue.club.annotation.AuthDescription;
import org.blue.club.annotation.DescriptionType;
import org.blue.club.annotation.PermissionDescription;
import org.blue.club.entities.vo.data.AuthVo;
import org.blue.club.entities.vo.data.ClubAuthVo;
import org.blue.club.entities.vo.data.ClubVo;
import org.mmga.spring.boot.starter.annotation.VoFieldMapper;
import org.mmga.spring.boot.starter.interfaces.IdHolder;
@Data
public class User implements IdHolder {
@Id
private Long id;
private String name;
@Nullable
private String avatar;
@RelationManyToOne(selfField = "auth", targetField = "id")
@VoFieldMapper("auths")
private AuthVo auth;
@RelationManyToOne(joinTable = "club_user_auth", selfField = "id", joinSelfColumn = "user_id", targetField = "id", joinTargetColumn = "club_id")
private ClubVo club;
@RelationManyToOne(joinTable = "club_user_auth", selfField = "id", joinSelfColumn = "user_id", targetField = "id", joinTargetColumn = "auth_id")
private ClubAuthVo clubAuth;
public boolean hasPermission(long id) {
return this.hasPermission(DescriptionType.NORMAL, id);
}
public boolean hasPermission(DescriptionType type, long id) {
return (type.equals(DescriptionType.CLUB) ? clubAuth : auth).hasPermission(id);
}
public boolean hasPermission(PermissionDescription description) {
return hasPermission(description.type(), description.value());
}
public boolean hasSuperAdminPermission() {
return this.hasPermission(3);
}
public boolean hasAuth(AuthDescription description) {
long authId = description.value();
return (description.type().equals(DescriptionType.CLUB) ? clubAuth.getId() : auth.getId()) == authId;
}
}

View File

@ -0,0 +1,14 @@
package org.blue.club.entities.dto.user.req;
import io.swagger.v3.oas.annotations.media.Schema;
import org.apache.commons.codec.digest.DigestUtils;
@Schema(name = "用户登录参数")
public record LoginRequest(@Schema(description = "用户名") String username,
@Schema(description = "用户密码使用MD5摘要后的hex字符串") String password,
@Schema(description = "验证码密钥") String key,
@Schema(description = "验证码") String code) {
public String sha1HexPassword() {
return DigestUtils.sha1Hex(this.password);
}
}

View File

@ -0,0 +1,15 @@
package org.blue.club.entities.dto.user.req;
import io.swagger.v3.oas.annotations.media.Schema;
import org.apache.commons.codec.digest.DigestUtils;
@Schema(name = "用户注册参数")
public record RegisterRequest(@Schema(description = "用户名") String username,
@Schema(description = "用户密码使用MD5摘要后的hex字符串") String password,
@Schema(description = "注册用户的权限组ID") Long auth,
@Schema(description = "验证码密钥") String key,
@Schema(description = "验证码") String code) {
public String sha1HexPassword() {
return DigestUtils.sha1Hex(this.password);
}
}

View File

@ -0,0 +1,8 @@
package org.blue.club.entities.dto.user.req;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(name = "用户修改权限组请求体")
public record UserChangeAuthRequest(@Schema(description = "被修改的用户ID") long id,
@Schema(description = "新的权限组ID") long authId) {
}

View File

@ -0,0 +1,26 @@
package org.blue.club.entities.dto.user.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.commons.codec.digest.DigestUtils;
import java.util.Objects;
@Schema(name = "用户修改密码请求体")
@Data
@AllArgsConstructor
public class UserChangePasswordRequest {
@Schema(description = "被修改的用户ID")
private final Long id;
@Schema(description = "修改后的密码MD5值")
private final String newPassword;
@Schema(description = "修改前的密码MD5值", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String oldPassword;
public UserChangePasswordRequest sha1HexPassword() {
String oldPassword = Objects.isNull(this.oldPassword) ? null : DigestUtils.sha1Hex(this.oldPassword);
String newPassword = Objects.isNull(this.newPassword) ? null : DigestUtils.sha1Hex(this.newPassword);
return new UserChangePasswordRequest(this.id, oldPassword, newPassword);
}
}

View File

@ -0,0 +1,18 @@
package org.blue.club.entities.dto.user.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@Schema(name = "用户修改用户名请求体")
@Data
@AllArgsConstructor
@RequiredArgsConstructor
public class UserRenameRequest {
@Schema(description = "新用户名")
private final String newName;
@Schema(description = "用户ID", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private Long id;
}

View File

@ -0,0 +1,8 @@
package org.blue.club.entities.dto.user.resp;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "验证码返回值")
public record VerifyCodeResponse(@Schema(description = "验证码图像base64") String img,
@Schema(description = "验证码验证密钥") String key) {
}

View File

@ -1,4 +1,4 @@
package org.mmga.clubs.entities.verify; package org.blue.club.entities.vo;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash; import org.springframework.data.redis.core.RedisHash;

View File

@ -0,0 +1,6 @@
package org.blue.club.entities.vo.abs;
public interface IPermission {
String getName();
long getId();
}

View File

@ -0,0 +1,15 @@
package org.blue.club.entities.vo.abs;
import java.util.List;
public interface PermissionsHolder {
String getName();
long getId();
List<? extends IPermission> getPermissions();
default boolean hasPermission(long permissionId) {
return this.getPermissions().stream().anyMatch(p -> p.getId() == permissionId);
}
}

View File

@ -0,0 +1,16 @@
package org.blue.club.entities.vo.data;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@Table("auth_permission")
public class AuthPermissionVo extends BaseDataVo {
@Column("auth_id")
private Long authId;
@Column("permission_id")
private Long permissionId;
}

View File

@ -0,0 +1,24 @@
package org.blue.club.entities.vo.data;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.RelationManyToMany;
import com.mybatisflex.annotation.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.blue.club.entities.vo.abs.PermissionsHolder;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Table("auth")
@Data
public class AuthVo extends BaseDataVo implements PermissionsHolder {
@Id
private long id;
private String name;
@RelationManyToMany(joinTable = "auth_permission",
selfField = "id", joinSelfColumn = "auth_id",
targetField = "id", joinTargetColumn = "permission_id"
)
private List<PermissionVo> permissions;
}

View File

@ -1,4 +1,4 @@
package org.mmga.clubs.entities.user.change; package org.blue.club.entities.vo.data;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash; import org.springframework.data.redis.core.RedisHash;
@ -7,5 +7,5 @@ import org.springframework.data.redis.core.index.Indexed;
import java.util.Date; import java.util.Date;
@RedisHash("avatar") @RedisHash("avatar")
public record UserAvatarOperationVo(@Id String operationCode, @Indexed String avatarSHA, @Indexed int userId, @Indexed Date createTime) { public record AvatarTmpVo(@Id String operationCode, @Indexed String sha1, @Indexed Date createTime) {
} }

View File

@ -0,0 +1,12 @@
package org.blue.club.entities.vo.data;
import com.mybatisflex.annotation.Column;
import java.util.Date;
public class BaseDataVo {
@Column(value = "create_time", onUpdateValue = "now()", onInsertValue = "now()")
protected Date createTime;
@Column(value = "update_time", onUpdateValue = "now()", onInsertValue = "now()")
protected Date updateTime;
}

View File

@ -0,0 +1,20 @@
package org.blue.club.entities.vo.data;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Table;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Table("club_auth_permission")
@Schema(name = "社团权限/权限组关联")
@Data
public class ClubAuthPermissionVo extends BaseDataVo {
@Schema(description = "权限组ID")
@Column("auth_id")
private long authId;
@Schema(description = "权限ID")
@Column("permission_id")
private long permissionId;
}

View File

@ -0,0 +1,28 @@
package org.blue.club.entities.vo.data;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.RelationManyToMany;
import com.mybatisflex.annotation.Table;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.blue.club.entities.vo.abs.PermissionsHolder;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Table("club_auth")
@Data
@Schema(name = "社团权限")
public class ClubAuthVo extends BaseDataVo implements PermissionsHolder {
@Schema(description = "社团权限ID")
@Id
private long id;
@Schema(description = "社团权限名称")
private String name;
@RelationManyToMany(joinTable = "club_auth_permission",
selfField = "id", joinSelfColumn = "auth_id",
targetField = "id", joinTargetColumn = "permission_id"
)
private List<ClubPermissionVo> permissions;
}

View File

@ -0,0 +1,20 @@
package org.blue.club.entities.vo.data;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.Table;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.blue.club.entities.vo.abs.IPermission;
@EqualsAndHashCode(callSuper = true)
@Table("club_permission")
@Schema(name = "社团权限")
@Data
public class ClubPermissionVo extends BaseDataVo implements IPermission {
@Id
@Schema(name = "权限ID")
private long id;
@Schema(name = "权限名称")
private String name;
}

View File

@ -0,0 +1,18 @@
package org.blue.club.entities.vo.data;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Table("club_user_auth")
@Data
public class ClubUserAuthVo extends BaseDataVo {
@Column("user_id")
private Long userId;
@Column("club_id")
private Long clubId;
@Column("auth_id")
private Long authId;
}

View File

@ -0,0 +1,17 @@
package org.blue.club.entities.vo.data;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@Table("club")
public class ClubVo extends BaseDataVo {
@Id
private Long id;
private String name;
private String commit;
private String avatar;
}

View File

@ -0,0 +1,16 @@
package org.blue.club.entities.vo.data;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.blue.club.entities.vo.abs.IPermission;
@EqualsAndHashCode(callSuper = true)
@Table("permission")
@Data
public class PermissionVo extends BaseDataVo implements IPermission {
@Id
private long id;
private String name;
}

View File

@ -0,0 +1,31 @@
package org.blue.club.entities.vo.data;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.RelationManyToOne;
import com.mybatisflex.annotation.Table;
import jakarta.annotation.Nullable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.mmga.spring.boot.starter.annotation.VoIgnore;
@EqualsAndHashCode(callSuper = true)
@Data
@Table("user")
public class UserVo extends BaseDataVo {
@Id(keyType = KeyType.Auto)
private Long id;
private String name;
@VoIgnore
private String password;
@VoIgnore
private Long auth;
@Nullable
private String avatar;
@RelationManyToOne(selfField = "auth", targetField = "id")
private AuthVo auths;
@RelationManyToOne(joinTable = "club_user_auth", selfField = "id", joinSelfColumn = "user_id", targetField = "id", joinTargetColumn = "club_id")
private ClubVo club;
@RelationManyToOne(joinTable = "club_user_auth", selfField = "id", joinSelfColumn = "user_id", targetField = "id", joinTargetColumn = "auth_id")
private ClubAuthVo clubAuth;
}

View File

@ -0,0 +1,14 @@
package org.blue.club.exceptions;
import lombok.Getter;
import org.mmga.spring.boot.starter.entities.Result;
@Getter
public class ServerException extends RuntimeException {
private final Result<Void> result;
public ServerException(Result<Void> result) {
super(result.getMsg());
this.result = result;
}
}

View File

@ -0,0 +1,56 @@
package org.blue.club.schedule;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.blue.club.dao.redis.AvatarOperationDao;
import org.blue.club.dao.redis.VerifyDao;
import org.blue.club.entities.vo.VerifyVo;
import org.blue.club.entities.vo.data.AvatarTmpVo;
import org.blue.club.utils.iop.FileUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.io.File;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
@Slf4j
@Component
@RequiredArgsConstructor
public class RemoveExpireDataSchedule {
private final AvatarOperationDao avatarDao;
private final File tmpFolder;
private final VerifyDao verifyDao;
private final FileUtils fileUtils;
@Scheduled(fixedDelay = 1000)
public void removeTempAvatar() {
Instant now = Instant.now();
for (AvatarTmpVo avatarTmpVo : avatarDao.findAll()) {
Date time = avatarTmpVo.createTime();
Instant instant = time.toInstant();
Instant expireTime = instant.plus(15, ChronoUnit.MINUTES);
if (expireTime.isAfter(now)) {
String s = avatarTmpVo.operationCode();
File deleteFile = new File(tmpFolder, s);
if (deleteFile.exists()) {
fileUtils.tryDeleteOrDeleteOnExit(deleteFile);
}
avatarDao.delete(avatarTmpVo);
}
}
}
@Scheduled(fixedDelay = 1000)
public void removeExpiredVerify() {
Instant now = Instant.now();
for (VerifyVo verifyVo : verifyDao.findAll()) {
Instant instant = verifyVo.createTime().toInstant();
Instant expireTime = instant.plus(15, ChronoUnit.MINUTES);
if (expireTime.isAfter(now)) {
verifyDao.delete(verifyVo);
}
}
}
}

View File

@ -0,0 +1,91 @@
package org.blue.club.services;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.tomcat.util.buf.HexUtils;
import org.blue.club.dao.redis.AvatarOperationDao;
import org.blue.club.entities.vo.data.AvatarTmpVo;
import org.blue.club.exceptions.ServerException;
import org.blue.club.utils.iop.FileUtils;
import org.mmga.spring.boot.starter.entities.Result;
import org.mmga.spring.boot.starter.utils.RandomUtils;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.Date;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class AvatarServices {
private final RandomUtils randomUtils;
private final File tmpFolder;
private final File avatarFolder;
private final AvatarOperationDao avatarOperationDao;
private final FileUtils fileUtils;
@SneakyThrows
public Result<String> changeAvatar(MultipartFile file) {
String s = randomUtils.generatorRandomFileName(32);
DigestInputStream digestInputStream = new DigestInputStream(file.getInputStream(), DigestUtils.getSha1Digest());
File tmpFile = new File(tmpFolder, s);
try (FileOutputStream fos = new FileOutputStream(tmpFile)) {
digestInputStream.transferTo(fos);
}
MessageDigest messageDigest = digestInputStream.getMessageDigest();
String sha1Hex = HexUtils.toHexString(messageDigest.digest());
avatarOperationDao.save(new AvatarTmpVo(s, sha1Hex, new Date()));
return Result.success("上传成功", s);
}
@SneakyThrows
public void getAvatar(HttpServletResponse response, String sha1) {
File targetFile = new File(avatarFolder, sha1);
if (!targetFile.getAbsolutePath().startsWith(avatarFolder.getAbsolutePath())) {
Result.failed(HttpStatus.NOT_FOUND, "文件不存在!").writeToResponse(response);
return;
}
if (!targetFile.exists()) {
Result.failed(HttpStatus.NOT_FOUND, "文件不存在!").writeToResponse(response);
return;
}
try (FileInputStream fileInputStream = new FileInputStream(targetFile)) {
response.setContentType("image/png");
response.setHeader("Content-Disposition", ContentDisposition.attachment().filename(sha1 + ".png").build().toString());
ServletOutputStream outputStream = response.getOutputStream();
StreamUtils.copy(fileInputStream, outputStream);
}
}
public Optional<String> permanentSaveAvatar(String avatarOperationCode) {
Optional<AvatarTmpVo> byId = avatarOperationDao.findById(avatarOperationCode);
if (byId.isEmpty()) return Optional.empty();
AvatarTmpVo avatarTmpVo = byId.get();
File tmpFile = new File(tmpFolder, avatarOperationCode);
String sha1 = avatarTmpVo.sha1();
File targetFile = new File(avatarFolder, sha1);
changeAvatarFile(tmpFile, targetFile);
avatarOperationDao.deleteById(avatarOperationCode);
return Optional.of(sha1);
}
public void changeAvatarFile(File tmpFile, File targetFile) {
if (targetFile.exists()) {
fileUtils.tryDeleteOrDeleteOnExit(tmpFile);
return;
}
if (!tmpFile.renameTo(targetFile))
throw new ServerException(Result.failed(HttpStatus.INTERNAL_SERVER_ERROR, "移动头像文件失败!请联系管理员处理"));
}
}

View File

@ -0,0 +1,175 @@
package org.blue.club.services;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryCondition;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.blue.club.dao.ClubDao;
import org.blue.club.dao.ClubUserAuthDao;
import org.blue.club.dao.UserDao;
import org.blue.club.entities.dto.club.req.*;
import org.blue.club.entities.dto.user.User;
import org.blue.club.entities.vo.data.ClubAuthVo;
import org.blue.club.entities.vo.data.ClubUserAuthVo;
import org.blue.club.entities.vo.data.ClubVo;
import org.blue.club.entities.vo.data.UserVo;
import org.blue.club.utils.NullableUtils;
import org.mmga.spring.boot.starter.entities.PagerData;
import org.mmga.spring.boot.starter.entities.Result;
import org.mmga.spring.boot.starter.exception.AuthorizationException;
import org.mmga.spring.boot.starter.utils.VoUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
import static com.mybatisflex.core.query.QueryMethods.select;
import static org.blue.club.entities.vo.data.table.ClubUserAuthVoTableDef.CLUB_USER_AUTH_VO;
import static org.blue.club.entities.vo.data.table.ClubVoTableDef.CLUB_VO;
import static org.blue.club.entities.vo.data.table.UserVoTableDef.USER_VO;
@Service
@RequiredArgsConstructor
@Slf4j
public class ClubServices {
private final ClubDao clubDao;
private final ClubUserAuthDao clubUserAuthDao;
private final AvatarServices avatarServices;
private final UserDao userDao;
private final VoUtils voUtils;
public Result<PagerData<ClubVo>> getClubs(Integer page, Integer num) {
Page<ClubVo> data = clubDao.paginate(page, num, QueryCondition.createEmpty());
return Result.success(new PagerData<>(data.getTotalRow(), data.getRecords()));
}
public Result<ClubVo> createClub(CreateClubRequest request) {
if (clubDao.selectCountByCondition(CLUB_VO.NAME.eq(clubDao)) > 0)
return Result.failed(HttpStatus.CONFLICT, "社团名称已存在!");
ClubVo clubVo = new ClubVo();
clubVo.setName(request.name());
return Result.success(clubVo);
}
@Transactional
public Result<Boolean> clubAddUser(User user, ClubAddUserRequest request) {
long requestClubId = request.clubId();
long userClubId = -1;
ClubVo userClub = user.getClub();
if (userClub != null) {
userClubId = userClub.getId();
}
boolean isUserSuperAdmin = user.hasSuperAdminPermission();
if (userClubId != requestClubId && !isUserSuperAdmin)
throw new AuthorizationException(Result.failed(HttpStatus.FORBIDDEN, "权限不足!"));
long userClubAuthId = -1;
ClubAuthVo clubAuth = user.getClubAuth();
if (clubAuth != null) {
userClubAuthId = clubAuth.getId();
}
long requestClubAuthId = request.clubAuthId();
if (requestClubAuthId >= userClubAuthId && !isUserSuperAdmin)
throw new AuthorizationException(Result.failed(HttpStatus.FORBIDDEN, "权限不足!"));
long requestUserId = request.userId();
if (clubUserAuthDao.selectCountByCondition(CLUB_USER_AUTH_VO.USER_ID.eq(requestUserId)) > 0)
return Result.failed(HttpStatus.CONFLICT, "此用户已在社团中");
ClubUserAuthVo clubUserAuthVo = new ClubUserAuthVo();
clubUserAuthVo.setClubId(requestClubId);
clubUserAuthVo.setUserId(requestUserId);
clubUserAuthVo.setAuthId(requestClubAuthId);
clubUserAuthDao.insert(clubUserAuthVo);
return Result.success(true);
}
public Result<PagerData<User>> clubGetUsers(Integer page, Integer num, Long clubId) {
if (clubDao.selectCountByCondition(CLUB_VO.ID.eq(clubId)) == 0)
return Result.failed(HttpStatus.NOT_FOUND, "社团不存在!");
Page<ClubUserAuthVo> paginate = clubUserAuthDao.paginate(page, num, select(CLUB_USER_AUTH_VO.ALL_COLUMNS).where(CLUB_USER_AUTH_VO.CLUB_ID.eq(clubId)).orderBy(CLUB_USER_AUTH_VO.AUTH_ID.asc(), CLUB_USER_AUTH_VO.USER_ID.asc()));
long totalRow = paginate.getTotalRow();
if (totalRow == 0) return Result.success(new PagerData<>(0, List.of()));
List<UserVo> userVos = userDao.selectListWithRelationsByQuery(select(USER_VO.ALL_COLUMNS).from(USER_VO).where(USER_VO.ID.in(paginate.getRecords().stream().map(ClubUserAuthVo::getUserId).toList())));
return Result.success(new PagerData<>(totalRow, userVos.stream().map(e -> voUtils.vo2DtoSafe(e, User.class)).toList()));
}
public Result<Boolean> clubRemoveUser(User user, long clubId, long userId) {
ClubUserAuthVo clubUserAuthVo = clubUserAuthDao.selectOneByCondition(CLUB_USER_AUTH_VO.USER_ID.eq(userId).and(CLUB_USER_AUTH_VO.CLUB_ID.eq(clubId)));
if (clubUserAuthVo == null) return Result.failed(HttpStatus.NOT_FOUND, "未知用户");
boolean isSuperAdmin = user.hasSuperAdminPermission();
ClubVo club = user.getClub();
long userClubId = NullableUtils.ifNonNullDoElse(club, ClubVo::getId, clubId);
if (userClubId != clubId && !isSuperAdmin)
throw new AuthorizationException(Result.failed(HttpStatus.FORBIDDEN, "权限不足!"));
Long authId = clubUserAuthVo.getAuthId();
ClubAuthVo clubAuth = user.getClubAuth();
Long clubAuthId = NullableUtils.ifNonNullDoElse(clubAuth, ClubAuthVo::getId, -1L);
if (clubAuthId <= authId && !isSuperAdmin)
throw new AuthorizationException(Result.failed(HttpStatus.FORBIDDEN, "权限不足!"));
clubUserAuthDao.deleteByCondition(CLUB_USER_AUTH_VO.USER_ID.eq(userId).and(CLUB_USER_AUTH_VO.CLUB_ID.eq(clubId)));
return Result.success(true);
}
public Result<Boolean> clubChangeUserAuth(User user, ClubChangeUserAuthRequest request) {
long targetUserId = request.userId();
long targetClubId = request.clubId();
ClubUserAuthVo clubUserAuthVo = clubUserAuthDao.selectOneByCondition(CLUB_USER_AUTH_VO.USER_ID.eq(targetUserId).and(CLUB_USER_AUTH_VO.CLUB_ID.eq(targetClubId)));
if (clubUserAuthVo == null) return Result.failed(HttpStatus.NOT_FOUND, "未知用户");
long originalClubAuthId = NullableUtils.ifNonNullDoElse(clubUserAuthVo, ClubUserAuthVo::getAuthId, -1L);
long targetClubAuthId = request.newAuthId();
long operatorClubAuthId = NullableUtils.ifNonNullDoElse(user.getClubAuth(), ClubAuthVo::getId, -1L);
long operatorClubId = NullableUtils.ifNonNullDoElse(user.getClub(), ClubVo::getId, -1L);
boolean isSuperAdmin = user.hasSuperAdminPermission();
// 不能修改和此用户社团不同的社团的用户
if (operatorClubId != targetClubId && !isSuperAdmin)
throw new AuthorizationException(Result.failed(HttpStatus.FORBIDDEN, "权限不足!"));
// 不能修改原始权限比操作者更高的用户
if (originalClubAuthId >= operatorClubAuthId && !isSuperAdmin)
throw new AuthorizationException(Result.failed(HttpStatus.FORBIDDEN, "权限不足!"));
// 当目标权限比操作者更高时不允许修改
if (targetClubAuthId >= operatorClubAuthId && !isSuperAdmin)
throw new AuthorizationException(Result.failed(HttpStatus.FORBIDDEN, "权限不足!"));
clubUserAuthVo.setAuthId(targetClubAuthId);
clubUserAuthDao.updateByCondition(clubUserAuthVo, CLUB_USER_AUTH_VO.USER_ID.eq(targetUserId).and(CLUB_USER_AUTH_VO.CLUB_ID.eq(targetClubId)));
return Result.success(true);
}
public Result<Boolean> changeAvatar(String avatarOperationCode, long clubId, User user) {
long operatorUserClubId = NullableUtils.ifNonNullDoElse(user.getClub(), ClubVo::getId, -1L);
if (clubId != operatorUserClubId && !user.hasSuperAdminPermission())
throw new AuthorizationException(Result.failed(HttpStatus.FORBIDDEN, "权限不足!"));
Optional<String> result = avatarServices.permanentSaveAvatar(avatarOperationCode);
if (result.isEmpty()) return Result.failed(HttpStatus.NOT_FOUND, "操作码错误或过期");
ClubVo clubVo = new ClubVo();
clubVo.setId(clubId);
clubVo.setAvatar(result.get());
clubDao.update(clubVo);
return Result.success(true);
}
public Result<Boolean> changeName(ChangeClubNameRequest request) {
long clubId = request.clubId();
String newName = request.name();
ClubVo clubVo = new ClubVo();
clubVo.setId(clubId);
clubVo.setName(newName);
clubDao.update(clubVo);
return Result.success(true);
}
public Result<Boolean> changeCommit(ChangeClubCommitRequest request, User user) {
long operatorUserClubId = NullableUtils.ifNonNullDoElse(user.getClub(), ClubVo::getId, -1L);
long clubId = request.clubId();
if (clubId != operatorUserClubId && !user.hasSuperAdminPermission())
throw new AuthorizationException(Result.failed(HttpStatus.FORBIDDEN, "权限不足!"));
ClubVo clubVo = new ClubVo();
clubVo.setId(clubId);
clubVo.setCommit(request.commit());
clubDao.update(clubVo);
return Result.success(true);
}
public Result<ClubVo> getClub(long id) {
return Result.success(clubDao.selectOneById(id));
}
}

View File

@ -0,0 +1,168 @@
package org.blue.club.services;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.blue.club.dao.UserDao;
import org.blue.club.dao.redis.VerifyDao;
import org.blue.club.entities.dto.user.User;
import org.blue.club.entities.dto.user.req.*;
import org.blue.club.entities.dto.user.resp.VerifyCodeResponse;
import org.blue.club.entities.vo.VerifyVo;
import org.blue.club.entities.vo.data.UserVo;
import org.blue.club.utils.iop.VerifyCodeUtils;
import org.mmga.spring.boot.starter.entities.PagerData;
import org.mmga.spring.boot.starter.entities.Result;
import org.mmga.spring.boot.starter.exception.AuthorizationException;
import org.mmga.spring.boot.starter.properties.Env;
import org.mmga.spring.boot.starter.properties.MainProperties;
import org.mmga.spring.boot.starter.utils.VoUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
import static com.mybatisflex.core.query.QueryMethods.select;
import static org.blue.club.entities.vo.data.table.ClubUserAuthVoTableDef.CLUB_USER_AUTH_VO;
import static org.blue.club.entities.vo.data.table.UserVoTableDef.USER_VO;
@Slf4j
@Service
@RequiredArgsConstructor
public class UserServices {
private final UserDao userDao;
private final VerifyDao verifyDao;
private final VoUtils voUtils;
private final VerifyCodeUtils verifyCodeUtils;
private final MainProperties mainProperties;
private final AvatarServices avatarServices;
private boolean isWrongVerifyCode(String key, String code) {
if (mainProperties.getEnv().equals(Env.DEV)) return false;
Optional<VerifyVo> targetCode = verifyDao.findById(key);
if (targetCode.isEmpty()) {
return true;
}
verifyDao.deleteById(key);
return !targetCode.get().answer().equals(code);
}
@Transactional
public Result<User> login(LoginRequest loginRequest) {
if (this.isWrongVerifyCode(loginRequest.key(), loginRequest.code())) {
return Result.failed(HttpStatus.FORBIDDEN, "错误的验证码");
}
UserVo userVo = userDao.selectOneWithRelationsByCondition(USER_VO.NAME.eq(loginRequest.username()).and(USER_VO.PASSWORD.eq(loginRequest.sha1HexPassword())));
if (userVo == null) return Result.failed(HttpStatus.NOT_FOUND, "用户不存在!");
User user = voUtils.vo2DtoSafe(userVo, User.class);
return Result.success(user);
}
@Transactional
public Result<User> createUser(RegisterRequest request) {
if (this.isWrongVerifyCode(request.key(), request.code())) {
return Result.failed(HttpStatus.FORBIDDEN, "错误的验证码");
}
if (userDao.selectCountByCondition(USER_VO.NAME.eq(request.username())) > 0) {
return Result.failed(HttpStatus.CONFLICT, "用户已存在");
}
UserVo userVo = new UserVo();
userVo.setName(request.username());
userVo.setPassword(request.sha1HexPassword());
userVo.setAuth(request.auth());
userDao.insert(userVo);
System.out.println(userVo);
UserVo insertedUserData = userDao.selectOneWithRelationsById(userVo.getId());
return Result.success(voUtils.vo2DtoSafe(insertedUserData, User.class));
}
public Result<PagerData<User>> getAllUserInfo(int num, int page, boolean onlyNoClub) {
QueryWrapper from = select(USER_VO.ALL_COLUMNS).from(USER_VO);
if (onlyNoClub) {
from.leftJoin(CLUB_USER_AUTH_VO).on(CLUB_USER_AUTH_VO.USER_ID.eq(USER_VO.ID)).where(CLUB_USER_AUTH_VO.CLUB_ID.isNull());
}
Page<UserVo> userVoPage = userDao.paginateWithRelations(page, num, from);
long totalRow = userVoPage.getTotalRow();
List<UserVo> records = userVoPage.getRecords();
return Result.success(new PagerData<>(totalRow, records.stream().map(e -> voUtils.vo2DtoSafe(e, User.class)).toList()));
}
public Result<Boolean> changeUsername(UserRenameRequest renameVo, User user) {
Long id = renameVo.getId();
Long operatorId = user.getId();
if (id == null) id = operatorId;
if (!operatorId.equals(id) && !user.hasPermission(4L))
throw new AuthorizationException(Result.failed(HttpStatus.UNAUTHORIZED, "权限不足!"));
String s = renameVo.getNewName();
if (userDao.selectCountByCondition(USER_VO.NAME.eq(s)) > 0)
return Result.failed(HttpStatus.CONFLICT, "用户名已存在!");
UserVo userVo = new UserVo();
userVo.setId(id);
userVo.setName(s);
userDao.update(userVo);
return Result.success(true);
}
public Result<Boolean> changePassword(UserChangePasswordRequest changePasswordVo, User user) {
UserChangePasswordRequest shaHex = changePasswordVo.sha1HexPassword();
String newPassword = shaHex.getNewPassword();
String oldPassword = shaHex.getOldPassword();
Long id = changePasswordVo.getId();
UserVo newUserVo = new UserVo();
newUserVo.setId(id);
newUserVo.setPassword(newPassword);
if (user.hasPermission(4L)) {
userDao.update(newUserVo);
return Result.success(true);
}
Long operatorUserId = user.getId();
if (oldPassword == null || !id.equals(operatorUserId))
throw new AuthorizationException(Result.failed(HttpStatus.UNAUTHORIZED, "权限不足!"));
if (userDao.selectCountByCondition(USER_VO.ID.eq(operatorUserId)) <= 0)
return Result.failed(HttpStatus.NOT_FOUND, "旧密码不匹配");
userDao.update(newUserVo);
return Result.success(true);
}
public Result<Boolean> changeAuth(UserChangeAuthRequest userChangeAuthVo) {
UserVo userVo = new UserVo();
userVo.setId(userChangeAuthVo.id());
userVo.setAuth(userChangeAuthVo.authId());
userDao.update(userVo);
return Result.success(true);
}
public Result<Boolean> changeAvatar(String avatarOperationCode, long userId, User user) {
long operatorUserId = user.getId();
if (userId == -1) {
userId = operatorUserId;
}
if (userId != operatorUserId && !user.hasSuperAdminPermission())
throw new AuthorizationException(Result.failed(HttpStatus.FORBIDDEN, "权限不足!"));
Optional<String> result = avatarServices.permanentSaveAvatar(avatarOperationCode);
if (result.isEmpty()) return Result.failed(HttpStatus.NOT_FOUND, "操作码错误或过期");
UserVo userVo = new UserVo();
userVo.setId(userId);
userVo.setAvatar(result.get());
userDao.update(userVo);
return Result.success(true);
}
public Result<VerifyCodeResponse> getVerifyCode() {
VerifyCodeResponse verifyCodeResponse = verifyCodeUtils.generateVerifyCode();
return Result.success(verifyCodeResponse);
}
public Result<User> getUserInfo(int userId) {
UserVo userVo = userDao.selectOneWithRelationsById(userId);
if (userVo == null) return Result.failed("用户不存在!");
User user = voUtils.vo2DtoSafe(userVo, User.class);
return Result.success(user);
}
}

View File

@ -0,0 +1,12 @@
package org.blue.club.utils;
import jakarta.annotation.Nullable;
import java.util.function.Function;
public class NullableUtils {
public static <T, R> R ifNonNullDoElse(@Nullable T value, Function<? super T, ? extends R> handle, R defaultValue) {
if (value == null) return defaultValue;
return handle.apply(value);
}
}

View File

@ -1,11 +1,11 @@
package org.mmga.clubs.utils; package org.blue.club.utils;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import jakarta.websocket.Session; import jakarta.websocket.Session;
import lombok.NonNull; import lombok.NonNull;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.mmga.clubs.entities.chess.packet.BaseWebSocketPacket; import org.blue.club.entities.dto.chess.packet.BaseWebSocketPacket;
import java.util.Objects; import java.util.Objects;

View File

@ -0,0 +1,16 @@
package org.blue.club.utils.iop;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.File;
@Component
@Slf4j
public class FileUtils {
public void tryDeleteOrDeleteOnExit(File file) {
if (file.delete()) return;
log.warn("删除文件{}失败,尝试在退出时删除", file.getAbsolutePath());
file.deleteOnExit();
}
}

View File

@ -0,0 +1,64 @@
package org.blue.club.utils.iop;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.codec.binary.Base64OutputStream;
import org.blue.club.dao.redis.VerifyDao;
import org.blue.club.entities.dto.user.resp.VerifyCodeResponse;
import org.blue.club.entities.vo.VerifyVo;
import org.mmga.spring.boot.starter.utils.RandomUtils;
import org.springframework.stereotype.Component;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Random;
@Component
@RequiredArgsConstructor
public class VerifyCodeUtils {
public static final int width = 120;
public static final int height = 40;
public static final int fontSize = 25;
public static final Font mainFont = new Font("宋体", Font.BOLD, fontSize);
private final RandomUtils randomUtils;
private final VerifyDao verifyDao;
private void setToRandomColor(Graphics g) {
Random random = new Random();
g.setColor(new Color(20 + random.nextInt(120), 20 + random.nextInt(120), 20 + random.nextInt(120)));
}
@SneakyThrows
public VerifyCodeResponse generateVerifyCode() {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.createGraphics();
g.setFont(mainFont);
String text = randomUtils.generatorRandomFileName(4);
int x = 0;
for (char c : text.toCharArray()) {
Random random = new Random();
setToRandomColor(g);
g.drawString(c + "", 15 + x * 20, 20 + new Random().nextInt(10));
for (int j = 0; j < random.nextInt(3, 7); j++) {
setToRandomColor(g);
g.drawLine(random.nextInt(width), random.nextInt(height), random.nextInt(width), random.nextInt(height));
}
x++;
}
g.dispose();
String s = randomUtils.generatorRandomString(32);
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Base64OutputStream base64OutputStream = new Base64OutputStream(byteArrayOutputStream)) {
ImageIO.write(image, "png", base64OutputStream);
base64OutputStream.flush();
String base64 = byteArrayOutputStream.toString(StandardCharsets.UTF_8);
verifyDao.save(new VerifyVo(s, text, new Date()));
return new VerifyCodeResponse(base64, s);
}
}
}

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