Compare commits

...

2 Commits
main ... main

Author SHA1 Message Date
fzm 03f2c24543 1
2 months ago
fzm 88aa98130b 提交
2 months ago

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

@ -0,0 +1,22 @@
name: Build
on:
push:
branches:
- study_analysis
pull_request:
types: [opened, synchronize, reopened]
jobs:
sonarqube:
name: SonarQube
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
# The analysis requires to retrieve dependencies and build successfully
- name: Build
run: <mark><commands_to_build_your_project></mark>
- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@v6
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

53
src/.gitignore vendored

@ -0,0 +1,53 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
.vscode/
.VSCodeCounter/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
/lib/data/user_data.json
*.g.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
#linux server
/backend/spring.log
/backend/nohup.out

@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "a402d9a4376add5bc2d6b1e33e53edaae58c07f8"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: android
create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: ios
create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: linux
create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: macos
create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: web
create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: windows
create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

@ -0,0 +1,78 @@
# 辅导软件 - Tutoring Software
## 系统简介
这是一款基于Flutter开发的跨平台家教服务软件旨在连接学生、家长和教师提供便捷的辅导服务匹配和学习管理功能。
### 主要功能特点
- **多角色支持**:支持学生/家长和教师两种角色,满足不同用户需求
- **丰富的科目覆盖**:涵盖小学、初中、高中各学科,以及兴趣爱好、专业技能等多种辅导类别
- **精准筛选**:根据科目、评分、评价数量等条件筛选合适的教师
- **学习分析**:提供学习进度和成绩分析功能
- **实时聊天**:支持师生之间的即时沟通
- **教师评价**:学生可以对教师进行评价和评分
- **个人中心**:管理个人信息、学习记录和辅导关系
### 技术架构
- **前端框架**Flutter 3.35.6
- **前端语言**: Dart3.9.2
- **后端语言**Java17
- **后端框架**Springboot
- **状态管理**MobX
- **路由管理**Flutter Modular
- **本地存储**Hive
- **远程存储**PostgreSQL
- **UI组件**Material Design + GetWidget
- **多平台支持**Android、iOS、Web、Windows、macOS、Linux
## 环境配置
### 开发环境要求
- **Flutter SDK**3.35.6
- **Dart SDK**3.9.2 或更高版本
- **IDE推荐**
- Android Studio (推荐集成Flutter插件)
- Visual Studio Code (配合Flutter插件)
- **移动设备/模拟器**
- Android设备/Android模拟器 (API 21+)
- iOS设备/iOS模拟器 (iOS 11+)
### 安装步骤
1. **克隆项目**
```bash
git clone https://gitee.com/JiaErC/tutoring-software.git
cd tutoring-software
```
2. **运行项目**
-**flutter前端安装相关的依赖**
```bash
flutter pub get
# 首次运行的时候需要做的事情
dart run build_runner build # 或者dart run build_runner watch
```
- **springboot后端的运行方式**
```bash
mvn spring-boot:run
```
- **查找可以运行的设备**-
```bash
flutter devices
```
- Android模拟器/设备:
```bash
flutter run -d android
```
- Windows版本:
```bash
flutter run -d windows
```

@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

@ -0,0 +1,49 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.tutoring_software"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.tutoring_software"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
// 添加以下配置以支持64位架构
ndk {
abiFilters += "arm64-v8a"
abiFilters += "x86_64"
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

@ -0,0 +1,52 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 添加这一行网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 可选:允许检查网络状态 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:label="软件工程家教软件"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

@ -0,0 +1,6 @@
package com.example.tutoring_software;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
}

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<!-- 允许访问阿里云服务器 -->
<domain includeSubdomains="true">8.140.222.2</domain>
<!-- 可选:允许访问本地开发服务器 -->
<domain includeSubdomains="true">127.0.0.1</domain>
<domain includeSubdomains="true">10.0.2.2</domain> <!-- Android模拟器访问本地主机 -->
<domain includeSubdomains="true">192.168.18.128</domain> <!-- 虚拟机IP -->
</domain-config>
<base-config cleartextTrafficPermitted="false" />
</network-security-config>

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

@ -0,0 +1,25 @@
allprojects {
repositories {
google()
mavenCentral()
maven { url=uri("https://maven.aliyun.com/repository/public/") }
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip

@ -0,0 +1,26 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.9.1" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}
include(":app")

@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf

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

@ -0,0 +1,3 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip

295
src/backend/mvnw vendored

@ -0,0 +1,295 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.4
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
scriptDir="$(dirname "$0")"
scriptName="$(basename "$0")"
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
# Find the actual extracted directory name (handles snapshots where filename != directory name)
actualDistributionDir=""
# First try the expected directory name (for regular distributions)
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
actualDistributionDir="$distributionUrlNameMain"
fi
fi
# If not found, search for any directory with the Maven executable (for snapshots)
if [ -z "$actualDistributionDir" ]; then
# enable globbing to iterate over items
set +f
for dir in "$TMP_DOWNLOAD_DIR"/*; do
if [ -d "$dir" ]; then
if [ -f "$dir/bin/$MVN_CMD" ]; then
actualDistributionDir="$(basename "$dir")"
break
fi
fi
done
set -f
fi
if [ -z "$actualDistributionDir" ]; then
verbose "Contents of $TMP_DOWNLOAD_DIR:"
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
die "Could not find Maven distribution directory in extracted archive"
fi
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

189
src/backend/mvnw.cmd vendored

@ -0,0 +1,189 @@
<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.4
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@SET __MVNW_ERROR__=
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
@SET PSModulePath=
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
if (!$distributionUrl) {
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_M2_PATH = "$HOME/.m2"
if ($env:MAVEN_USER_HOME) {
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
}
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
}
$MAVEN_WRAPPER_DISTS = $null
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
} else {
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
}
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
if ($distributionSha256Sum) {
if ($USE_MVND) {
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
# Find the actual extracted directory name (handles snapshots where filename != directory name)
$actualDistributionDir = ""
# First try the expected directory name (for regular distributions)
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
$actualDistributionDir = $distributionUrlNameMain
}
# If not found, search for any directory with the Maven executable (for snapshots)
if (!$actualDistributionDir) {
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
if (Test-Path -Path $testPath -PathType Leaf) {
$actualDistributionDir = $_.Name
}
}
}
if (!$actualDistributionDir) {
Write-Error "Could not find Maven distribution directory in extracted archive"
}
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.tutoring-software</groupId>
<artifactId>backend</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>backend</name>
<description>家教软件的后端</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-security</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 添加 Apache Commons Lang 依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.18.0</version>
</dependency>
<!-- 添加 SLF4J 日志依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,15 @@
package com.tutoring_software.backend;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.tutoring_software.backend.uid_management.mapper")
public class BackendApplication {
public static void main(String[] args) {
SpringApplication.run(BackendApplication.class, args);
}
}

@ -0,0 +1,31 @@
package com.tutoring_software.backend;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
// 允许所有来源
config.addAllowedOriginPattern("*");
// 允许所有请求头
config.addAllowedHeader("*");
// 允许所有HTTP方法
config.addAllowedMethod("*");
// 允许携带凭证如cookie
config.setAllowCredentials(true);
// 设置预检请求的有效期(秒)
config.setMaxAge(3600L);
// 应用配置到所有路径
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}

@ -0,0 +1,82 @@
package com.tutoring_software.backend;
import com.tutoring_software.backend.uid_management.service.PhoneNumberUidService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloWorldController {
@Autowired
private PhoneNumberUidService phoneNumberUidService;
private static final Logger logger = LoggerFactory.getLogger(HelloWorldController.class);
@GetMapping("/hello")
public String helloWorld() {
String phoneNumber = "133124448678";
Long uid = 12336633L;
try {
boolean success = phoneNumberUidService.savePhoneNumberUid(phoneNumber, uid);
if (success) {
return "保存成功: 电话号码" + phoneNumber + " 与 UID " + uid + " 已关联";
} else {
return "保存失败,请重试";
}
} catch (Exception e) {
logger.error("保存电话号码与UID映射关系时发生错误", e);
return "保存时发生错误: " + e.getMessage();
}
}
//
// /**
// * 存储电话号码和UID的映射关系
// * 调用示例: POST /hello/save-phone-uid?phoneNumber=13812345678&uid=123456
// */
// @PostMapping("/hello/save-phone-uid")
// public String savePhoneNumberUid(@RequestParam String phoneNumber, @RequestParam Long uid) {
// try {
// boolean success = phoneNumberUidService.savePhoneNumberUid(phoneNumber, uid);
// if (success) {
// return "保存成功: 电话号码" + phoneNumber + " 与 UID " + uid + " 已关联";
// } else {
// return "保存失败,请重试";
// }
// } catch (Exception e) {
// return "保存时发生错误: " + e.getMessage();
// }
// }
//
// /**
// * 根据电话号码查询对应的UID
// * 调用示例: GET /hello/get-uid-by-phone?phoneNumber=13812345678
// */
// @GetMapping("/hello/get-uid-by-phone")
// public String getUidByPhoneNumber(@RequestParam String phoneNumber) {
// try {
// return phoneNumberUidService.getByPhoneNumber(phoneNumber) != null
// ? "查询结果: 电话号码" + phoneNumber + " 对应的UID是 " + phoneNumberUidService.getByPhoneNumber(phoneNumber).getUid()
// : "未找到该电话号码对应的UID";
// } catch (Exception e) {
// return "查询时发生错误: " + e.getMessage();
// }
// }
//
// /**
// * 根据UID查询对应的电话号码
// * 调用示例: GET /hello/get-phone-by-uid?uid=123456
// */
// @GetMapping("/hello/get-phone-by-uid")
// public String getPhoneByUid(@RequestParam Long uid) {
// try {
// return phoneNumberUidService.getByUid(uid) != null
// ? "查询结果: UID " + uid + " 对应的电话号码是 " + phoneNumberUidService.getByUid(uid).getPhoneNumber()
// : "未找到该UID对应的电话号码";
// } catch (Exception e) {
// return "查询时发生错误: " + e.getMessage();
// }
// }
}

@ -0,0 +1,92 @@
package com.tutoring_software.backend.uid_generate;
/*
* 64UID
* (1)->->ID->
* */
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.springframework.util.Assert;
public class BitsAllocator {
//总位数固定为64位
public static final int TOTAL_BITS = 1<<6;
//符号位数1位。时间戳位数工作节点ID位数序列号位数(这三个都由构造函数传入)
private int signBits = 1;
private final int timestampBits;
private final int workerIdBits;
private final int sequenceBits;
//最大值计算最大时间戳值最大工作节点ID值最大序列号值
private final long maxDeltaSeconds;
private final long maxWorkerId;
private final long maxSequence;
//位移计算时间戳左移位数工作节点ID左移位数
private final int timestampShift;
private final int workerIdShift;
//构造函数
public BitsAllocator(int timestampBits, int workerIdBits, int sequenceBits) {
// 验证总位数必须等于64位
int allocateTotalBits = signBits + timestampBits + workerIdBits + sequenceBits;
Assert.isTrue(allocateTotalBits == TOTAL_BITS, "allocate not enough 64 bits");
// 初始化各部分位数
this.timestampBits = timestampBits;
this.workerIdBits = workerIdBits;
this.sequenceBits = sequenceBits;
// 计算各部分最大值
this.maxDeltaSeconds = ~(-1L << timestampBits);
this.maxWorkerId = ~(-1L << workerIdBits);
this.maxSequence = ~(-1L << sequenceBits);
// i初始化位移量
this.timestampShift = workerIdBits + sequenceBits;
this.workerIdShift = sequenceBits;
}
//将时间戳、工作节点、序列号组合成64位UID
public long allocate(long deltaSeconds, long workerId, long sequence) {
return (deltaSeconds << timestampShift) | (workerId << workerIdShift) | sequence;
}
//getter方法
public int getSignBits() {
return signBits;
}
public int getTimestampBits() {
return timestampBits;
}
public int getWorkerIdBits() {
return workerIdBits;
}
public int getSequenceBits() {
return sequenceBits;
}
public long getMaxDeltaSeconds() {
return maxDeltaSeconds;
}
public long getMaxWorkerId() {
return maxWorkerId;
}
public long getMaxSequence() {
return maxSequence;
}
public int getTimestampShift() {
return timestampShift;
}
public int getWorkerIdShift() {
return workerIdShift;
}
//重写toString方法()
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}

@ -0,0 +1,82 @@
package com.tutoring_software.backend.uid_generate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.tutoring_software.backend.uid_generate.UidGenerator;
import com.tutoring_software.backend.uid_generate.exception.UidGenerateException;
@RestController
@RequestMapping("/api/uid")
public class UidController {
private static final Logger LOGGER = LoggerFactory.getLogger(UidController.class);
@Autowired
private UidGenerator uidGenerator;
/**
* UID
*/
@GetMapping("/generate")
public Result<Long> generateUid() {
try {
long uid = uidGenerator.getUID();
LOGGER.info("Generated UID: {}", uid);
return Result.success(uid);
} catch (UidGenerateException e) {
LOGGER.error("Failed to generate UID", e);
return Result.error("Failed to generate UID: " + e.getMessage());
}
}
/**
* UID
*/
@GetMapping("/parse")
public Result<String> parseUid(Long uid) {
if (uid == null) {
return Result.error("UID cannot be null");
}
try {
String parsed = uidGenerator.parseUID(uid);
LOGGER.info("Parsed UID: {}", parsed);
return Result.success(parsed);
} catch (Exception e) {
LOGGER.error("Failed to parse UID", e);
return Result.error("Failed to parse UID: " + e.getMessage());
}
}
// 统一响应结果封装类
public static class Result<T> {
private int code;
private String message;
private T data;
private Result(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public static <T> Result<T> success(T data) {
return new Result<>(200, "Success", data);
}
public static <T> Result<T> error(String message) {
return new Result<>(500, message, null);
}
// Getters and setters
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public T getData() { return data; }
public void setData(T data) { this.data = data; }
}
}

@ -0,0 +1,12 @@
package com.tutoring_software.backend.uid_generate;
import com.tutoring_software.backend.uid_generate.exception.UidGenerateException;
public interface UidGenerator {
//获取一个唯一的ID
long getUID() throws UidGenerateException;
//将生成的UID解析为构成元素的详细信息
String parseUID(long uid);
}

@ -0,0 +1,23 @@
package com.tutoring_software.backend.uid_generate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.tutoring_software.backend.uid_generate.UidGenerator;
import com.tutoring_software.backend.uid_generate.impl.DefaultUidGenerator;
import com.tutoring_software.backend.uid_generate.worker.WorkerIdAssigner;
@Configuration
public class UidGeneratorConfig {
@Bean
public UidGenerator uidGenerator(WorkerIdAssigner workerIdAssigner) {
DefaultUidGenerator uidGenerator = new DefaultUidGenerator();
uidGenerator.setWorkerIdAssigner(workerIdAssigner);
// 可以根据需要调整位分配策略
uidGenerator.setTimeBits(28); // 时间位数
uidGenerator.setWorkerBits(22); // 工作节点位数
uidGenerator.setSeqBits(13); // 序列号位数
uidGenerator.setEpochStr("2024-01-01"); // 设置纪元时间
return uidGenerator;
}
}

@ -0,0 +1,35 @@
package com.tutoring_software.backend.uid_generate.exception;
import java.io.Serial;
public class UidGenerateException extends RuntimeException{
//序列化版本标识符,用于确保序列化和反序列化的版本兼容性
@Serial
private static final long serialVersionUID = -27048199131316992L;
//父类无参构造方法
public UidGenerateException() {
super();
}
//带错误消息和原因的构造函数
public UidGenerateException(String message, Throwable cause) {
super(message, cause);
}
//支持带有错误消息的构造函数
public UidGenerateException(String message) {
super(message);
}
//支持消息格式化的构造函数
public UidGenerateException(String msgFormat, Object... args) {
super(String.format(msgFormat, args));
}
//仅仅带有异常原因的构造函数
public UidGenerateException(Throwable cause) {
super(cause);
}
}

@ -0,0 +1,162 @@
package com.tutoring_software.backend.uid_generate.impl;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.apache.commons.lang3.StringUtils;
import com.tutoring_software.backend.uid_generate.UidGenerator;
import com.tutoring_software.backend.uid_generate.exception.UidGenerateException;
import com.tutoring_software.backend.uid_generate.BitsAllocator;
import com.tutoring_software.backend.uid_generate.worker.WorkerIdAssigner;
import com.tutoring_software.backend.uid_generate.utils.UidDateUtils;
public class DefaultUidGenerator implements UidGenerator, InitializingBean {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultUidGenerator.class);
//时间位数、工作区位数、序列位数
protected int timeBits = 28;
protected int workerBits = 22;
protected int seqBits = 13;
//时间戳计算
protected String epochStr = "2016-05-20";
protected long epochSeconds = TimeUnit.MILLISECONDS.toSeconds(1463673600000L);
//位分配器和工作节点
protected BitsAllocator bitsAllocator;
protected long workerId;
//序列号和上次生成ID的时间戳(秒)
protected long sequence = 0L;
protected long lastSecond = -1L;
//工作节点ID分配器Spring注入
protected WorkerIdAssigner workerIdAssigner;
@Override
public void afterPropertiesSet() throws Exception {
//初始化位分配器
bitsAllocator = new BitsAllocator(timeBits, workerBits, seqBits);
//初始化工作节点ID
workerId = workerIdAssigner.assignWorkerId();
if (workerId > bitsAllocator.getMaxWorkerId()) {
throw new RuntimeException("Worker id " + workerId + " exceeds the max " + bitsAllocator.getMaxWorkerId());
}
LOGGER.info("初始化位分配器(1, {}, {}, {}) 作为工作节点ID:{}", timeBits, workerBits, seqBits, workerId);
}
//重写getUID()方法获取下一个ID
@Override
public long getUID() throws UidGenerateException {
try {
return nextId();
} catch (Exception e) {
LOGGER.error("产生唯一一个ID失败 ", e);
throw new UidGenerateException(e);
}
}
//重写parseUID()方法解析ID
@Override
public String parseUID(long uid) {
long totalBits = BitsAllocator.TOTAL_BITS;
long signBits = bitsAllocator.getSignBits();
long timestampBits = bitsAllocator.getTimestampBits();
long workerIdBits = bitsAllocator.getWorkerIdBits();
long sequenceBits = bitsAllocator.getSequenceBits();
//解析UID
long sequence = (uid << (totalBits - sequenceBits)) >>> (totalBits - sequenceBits);
long workerId = (uid << (timestampBits + signBits)) >>> (totalBits - workerIdBits);
long deltaSeconds = uid >>> (workerIdBits + sequenceBits);
Date thatTime = new Date(TimeUnit.SECONDS.toMillis(epochSeconds + deltaSeconds));
String thatTimeStr = UidDateUtils.formatByDateTimePattern(thatTime);
//使用JSON格式返回解析结果
return String.format("{\"UID\":\"%d\",\"timestamp\":\"%s\",\"workerId\":\"%d\",\"sequence\":\"%d\"}",
uid, thatTimeStr, workerId, sequence);
}
//寻找下一个 ID
protected synchronized long nextId() {
//获取当前时间戳
long currentSecond = getCurrentSecond();
// 处理时钟回拨异常
if (currentSecond < lastSecond) {
long refusedSeconds = lastSecond - currentSecond;
throw new UidGenerateException("Clock moved backwards. Refusing for %d seconds", refusedSeconds);
}
// 同一秒内递增序列号达到阈值则等待下一秒否则重置为0
if (currentSecond == lastSecond) {
sequence = (sequence + 1) & bitsAllocator.getMaxSequence();
// 等待下一秒
if (sequence == 0) {
currentSecond = getNextSecond(lastSecond);
}
// 在不同的秒内序列号重置为0
} else {
sequence = 0L;
}
lastSecond = currentSecond;
// 分配UID
return bitsAllocator.allocate(currentSecond - epochSeconds, workerId, sequence);
}
//获取下一个可用秒数,处理序列号溢出情况
private long getNextSecond(long lastTimestamp) {
long timestamp = getCurrentSecond();
while (timestamp <= lastTimestamp) {
timestamp = getCurrentSecond();
}
return timestamp;
}
//获取当前时间戳(秒),检查是否超出时间位数限制
private long getCurrentSecond() {
long currentSecond = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
if (currentSecond - epochSeconds > bitsAllocator.getMaxDeltaSeconds()) {
throw new UidGenerateException("Timestamp bits is exhausted. Refusing UID generate. Now: " + currentSecond);
}
return currentSecond;
}
//设置工作节点ID分配器
public void setWorkerIdAssigner(WorkerIdAssigner workerIdAssigner) {
this.workerIdAssigner = workerIdAssigner;
}
//设置时间位数
public void setTimeBits(int timeBits) {
if (timeBits > 0) {
this.timeBits = timeBits;
}
}
//设置工作节点位数
public void setWorkerBits(int workerBits) {
if (workerBits > 0) {
this.workerBits = workerBits;
}
}
//设置序列号位数
public void setSeqBits(int seqBits) {
if (seqBits > 0) {
this.seqBits = seqBits;
}
}
//设置纪元时间字符串
public void setEpochStr(String epochStr) {
if (StringUtils.isNotBlank(epochStr)) {
this.epochStr = epochStr;
this.epochSeconds = TimeUnit.MILLISECONDS.toSeconds(UidDateUtils.parseByDayPattern(epochStr).getTime());
}
}
}

@ -0,0 +1,56 @@
package com.tutoring_software.backend.uid_generate.utils;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
public abstract class UidDateUtils {
public static final String DAY_PATTERN = "yyyy-MM-dd";
public static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static final String DATETIME_MS_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS";
public static final Date DEFAULT_DATE = UidDateUtils.parseByDayPattern("1970-01-01");
public static Date parseByDayPattern(String str) {
return parseDate(str, DAY_PATTERN);
}
public static Date parseByDateTimePattern(String str) {
return parseDate(str, DATETIME_PATTERN);
}
public static Date parseDate(String str, String pattern) {
try {
return DateUtils.parseDate(str, new String[]{pattern});
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
public static String formatDate(Date date, String pattern) {
return DateFormatUtils.format(date, pattern);
}
public static String formatByDayPattern(Date date) {
if (date != null) {
return DateFormatUtils.format(date, DAY_PATTERN);
} else {
return null;
}
}
public static String formatByDateTimePattern(Date date) {
return DateFormatUtils.format(date, DATETIME_PATTERN);
}
public static String getCurrentDayByDayPattern() {
Calendar cal = Calendar.getInstance();
return formatByDayPattern(cal.getTime());
}
}

@ -0,0 +1,5 @@
package com.tutoring_software.backend.uid_generate.worker;
public interface WorkerIdAssigner {
long assignWorkerId();
}

@ -0,0 +1,21 @@
package com.tutoring_software.backend.uid_generate.worker.entity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.tutoring_software.backend.uid_generate.worker.WorkerIdAssigner;
@Component
public class DefaultWorkerIdAssigner implements WorkerIdAssigner {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultWorkerIdAssigner.class);
// 固定的工作节点ID单机环境下可以使用固定值
private static final long WORKER_ID = 1L;
@Override
public long assignWorkerId() {
LOGGER.info("Assigned worker ID: {}", WORKER_ID);
return WORKER_ID;
}
}

@ -0,0 +1,32 @@
package com.tutoring_software.backend.uid_management;
/**
*
*/
public class Result<T> {
private int code;
private String message;
private T data;
private Result(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public static <T> Result<T> success(T data) {
return new Result<>(200, "Success", data);
}
public static <T> Result<T> error(String message) {
return new Result<>(500, message, null);
}
// Getters and Setters
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public T getData() { return data; }
public void setData(T data) { this.data = data; }
}

@ -0,0 +1,103 @@
package com.tutoring_software.backend.uid_management.controller;
import com.tutoring_software.backend.uid_management.entity.CommentItem;
import com.tutoring_software.backend.uid_management.entity.Teacher;
import com.tutoring_software.backend.uid_management.service.CommentItemService;
import com.tutoring_software.backend.uid_management.service.TeacherService;
import com.tutoring_software.backend.uid_management.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.time.LocalDateTime;
/**
*
*/
@RestController
@RequestMapping("/api/comment")
public class CommentItemController {
private static final Logger LOGGER = LoggerFactory.getLogger(CommentItemController.class);
@Autowired
private CommentItemService commentItemService;
@Autowired
private TeacherService teacherService;
/**
* UID
*/
@GetMapping("/teacher/{teacherUid}")
public Result<List<CommentItem>> getCommentsByTeacher(@PathVariable Long teacherUid) {
LOGGER.info("需要查询老师{}的评论列表", teacherUid);
try {
List<CommentItem> result = commentItemService.getCommentsByTeacherUid(teacherUid);
if (result != null) {
return Result.success(result);
} else {
return Result.error("No comments found");
}
} catch (Exception e) {
LOGGER.error("Error getting comments by teacher UID", e);
return Result.error("Internal server error");
}
}
/**
* UID
*/
@GetMapping("/student/{studentUid}")
public Result<List<CommentItem>> getCommentsByStudent(@PathVariable Long studentUid) {
LOGGER.info("需要查询学生{}的评论列表", studentUid);
try {
List<CommentItem> result = commentItemService.getCommentsByStudentUid(studentUid);
if (result != null) {
return Result.success(result);
} else {
return Result.error("No comments found");
}
} catch (Exception e) {
LOGGER.error("Error getting comments by student UID", e);
return Result.error("Internal server error");
}
}
/**
*
*/
@PostMapping("/save")
public Result<String> saveComment(@RequestBody CommentItem commentItem) {
LOGGER.info("保存评论: 学生{} 评论老师{}", commentItem.getStudentUid(), commentItem.getTeacherUid());
try {
// 设置创建时间
if (commentItem.getCreatedAt() == null) {
commentItem.setCreatedAt(LocalDateTime.now());
}
boolean success = commentItemService.saveComment(commentItem);
if (success) {
LOGGER.info("成功保存评论: 学生{} 评论老师{}", commentItem.getStudentUid(), commentItem.getTeacherUid());
// 更新老师的评论数
Teacher teacher = teacherService.getByTeacherUid(commentItem.getTeacherUid());
if (teacher != null) {
double commentRating = Double.parseDouble(commentItem.getRating());
double currentRating = teacher.getRating();
double newRating = (currentRating * teacher.getComments() + commentRating)
/ (teacher.getComments() + 1);
int newComments = teacher.getComments() + 1;
teacherService.updateTeacher(commentItem.getTeacherUid(), newRating, newComments);
}
return Result.success("Save successful");
} else {
return Result.error("Save failed");
}
} catch (Exception e) {
LOGGER.error("Error saving comment", e);
return Result.error("Internal server error");
}
}
}

@ -0,0 +1,126 @@
package com.tutoring_software.backend.uid_management.controller;
import com.tutoring_software.backend.uid_management.Result;
import com.tutoring_software.backend.uid_management.entity.EmailUid;
import com.tutoring_software.backend.uid_management.service.EmailUidService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/email-uid")
public class EmailUidController {
private static final Logger LOGGER = LoggerFactory.getLogger(EmailUidController.class);
@Autowired
private EmailUidService emailUidService;
@GetMapping("/by-email/{email}")
public Result<EmailUid> getByEmail(@PathVariable String email) {
try {
EmailUid result = emailUidService.getByEmail(email);
if (result != null) {
return Result.success(result);
} else {
return Result.error("Email not found");
}
} catch (Exception e) {
LOGGER.error("Error getting by email", e);
return Result.error("Internal server error");
}
}
@GetMapping("/by-uid/{uid}")
public Result<EmailUid> getByUid(@PathVariable Long uid) {
try {
EmailUid result = emailUidService.getByUid(uid);
if (result != null) {
return Result.success(result);
} else {
return Result.error("UID not found");
}
} catch (Exception e) {
LOGGER.error("Error getting by UID", e);
return Result.error("Internal server error");
}
}
@PostMapping
public Result<EmailUid> save(@RequestBody EmailUid emailUid) {
try {
boolean success = emailUidService.saveEmailUid(emailUid.getEmail(), emailUid.getUid());
if (success) {
return Result.success(emailUid);
} else {
return Result.error("Save failed");
}
} catch (Exception e) {
LOGGER.error("Error saving email UID", e);
return Result.error("Internal server error");
}
}
@PostMapping("/save")
public Result<EmailUid> saveEmailUid(@RequestParam String email, @RequestParam Long uid) {
try {
boolean success = emailUidService.saveEmailUid(email, uid);
if (success) {
// 打印成功信息
LOGGER.info("Successfully saved EmailUid: email={}, uid={}", email, uid);
return Result.success(new EmailUid(email, uid));
} else {
return Result.error("Save failed");
}
} catch (Exception e) {
LOGGER.error("Error saving email UID", e);
return Result.error("Internal server error");
}
}
@PutMapping
public Result<String> updateEmailUid(@RequestParam String email, @RequestParam Long uid) {
try {
boolean success = emailUidService.updateByEmail(email, uid);
if (success) {
return Result.success("Update successful");
} else {
return Result.error("Update failed or email not found");
}
} catch (Exception e) {
LOGGER.error("Error updating email UID", e);
return Result.error("Internal server error");
}
}
@DeleteMapping("/delete/email/{email}")
public Result<String> deleteByEmail(@PathVariable String email) {
try {
boolean success = emailUidService.deleteByEmail(email);
if (success) {
return Result.success("Delete successful");
} else {
return Result.error("Delete failed or email not found");
}
} catch (Exception e) {
LOGGER.error("Error deleting by email", e);
return Result.error("Internal server error");
}
}
@DeleteMapping("/delete/uid/{uid}")
public Result<String> deleteByUid(@PathVariable Long uid) {
try {
boolean success = emailUidService.deleteByUid(uid);
if (success) {
return Result.success("Delete successful");
} else {
return Result.error("Delete failed or UID not found");
}
} catch (Exception e) {
LOGGER.error("Error deleting by UID", e);
return Result.error("Internal server error");
}
}
}

@ -0,0 +1,112 @@
package com.tutoring_software.backend.uid_management.controller;
import com.tutoring_software.backend.uid_management.entity.PhoneNumberUid;
import com.tutoring_software.backend.uid_management.service.PhoneNumberUidService;
import com.tutoring_software.backend.uid_management.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/phone-uid")
public class PhoneNumberUidController {
private static final Logger LOGGER = LoggerFactory.getLogger(PhoneNumberUidController.class);
@Autowired
private PhoneNumberUidService phoneNumberUidService;
@GetMapping("/by-phone/{phoneNumber}")
public Result<PhoneNumberUid> getByPhoneNumber(@PathVariable String phoneNumber) {
try {
PhoneNumberUid result = phoneNumberUidService.getByPhoneNumber(phoneNumber);
if (result != null) {
return Result.success(result);
} else {
return Result.error("Phone number not found");
}
} catch (Exception e) {
LOGGER.error("Error getting by phone number", e);
return Result.error("Internal server error");
}
}
@GetMapping("/by-uid/{uid}")
public Result<PhoneNumberUid> getByUid(@PathVariable Long uid) {
try {
PhoneNumberUid result = phoneNumberUidService.getByUid(uid);
if (result != null) {
return Result.success(result);
} else {
return Result.error("UID not found");
}
} catch (Exception e) {
LOGGER.error("Error getting by UID", e);
return Result.error("Internal server error");
}
}
@PostMapping("/save")
public Result<String> savePhoneNumberUid(@RequestParam String phoneNumber, @RequestParam Long uid) {
try {
boolean success = phoneNumberUidService.savePhoneNumberUid(phoneNumber, uid);
if (success) {
// 打印成功信息
LOGGER.info("Successfully saved PhoneNumberUid: phoneNumber={}, uid={}", phoneNumber, uid);
return Result.success("Save successful");
} else {
return Result.error("Save failed");
}
} catch (Exception e) {
LOGGER.error("Error saving phone number UID", e);
return Result.error("Internal server error");
}
}
@PutMapping
public Result<String> updatePhoneNumberUid(@RequestParam String phoneNumber, @RequestParam Long uid) {
try {
boolean success = phoneNumberUidService.updateByPhoneNumber(phoneNumber, uid);
if (success) {
return Result.success("Update successful");
} else {
return Result.error("Update failed or phone number not found");
}
} catch (Exception e) {
LOGGER.error("Error updating phone number UID", e);
return Result.error("Internal server error");
}
}
@DeleteMapping("/delete/phone/{phoneNumber}")
public Result<String> deleteByPhoneNumber(@PathVariable String phoneNumber) {
try {
boolean success = phoneNumberUidService.deleteByPhoneNumber(phoneNumber);
if (success) {
return Result.success("Delete successful");
} else {
return Result.error("Delete failed or phone number not found");
}
} catch (Exception e) {
LOGGER.error("Error deleting by phone number", e);
return Result.error("Internal server error");
}
}
@DeleteMapping("/delete/uid/{uid}")
public Result<String> deleteByUid(@PathVariable Long uid) {
try {
boolean success = phoneNumberUidService.deleteByUid(uid);
if (success) {
return Result.success("Delete successful");
} else {
return Result.error("Delete failed or UID not found");
}
} catch (Exception e) {
LOGGER.error("Error deleting by UID", e);
return Result.error("Internal server error");
}
}
}

@ -0,0 +1,129 @@
package com.tutoring_software.backend.uid_management.controller;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.tutoring_software.backend.uid_management.Result;
import com.tutoring_software.backend.uid_management.entity.TSRelationship;
import com.tutoring_software.backend.uid_management.service.TSRelationshipService;
/**
*
*/
@RestController
@RequestMapping("/api/ts-relationship")
public class TSRelationshipController {
private static final Logger LOGGER = LoggerFactory.getLogger(TSRelationshipController.class);
@Autowired
private TSRelationshipService tsRelationshipService;
/**
* UID
*/
@GetMapping("/student/{studentUid}")
public Result<List<TSRelationship>> getRelationshipsByStudent(@PathVariable Long studentUid) {
LOGGER.info("需要查询学生{}的师生关系列表", studentUid);
try {
List<TSRelationship> result = tsRelationshipService.selectByStudentUid(studentUid);
if (result != null) {
return Result.success(result);
} else {
return Result.error("No relationships found");
}
} catch (Exception e) {
LOGGER.error("Error getting relationships by student UID", e);
return Result.error("Internal server error");
}
}
/**
* UID
*/
@GetMapping("/teacher/{teacherUid}")
public Result<List<TSRelationship>> getRelationshipsByTeacher(@PathVariable Long teacherUid) {
LOGGER.info("需要查询老师{}的师生关系列表", teacherUid);
try {
List<TSRelationship> result = tsRelationshipService.selectByTeacherUid(teacherUid);
if (result != null) {
return Result.success(result);
} else {
return Result.error("No relationships found");
}
} catch (Exception e) {
LOGGER.error("Error getting relationships by teacher UID", e);
return Result.error("Internal server error");
}
}
/**
* UIDUID
*/
@GetMapping("/check")
public Result<TSRelationship> checkRelationship(@RequestParam Long studentUid, @RequestParam Long teacherUid) {
LOGGER.info("检查学生{}和老师{}的关系", studentUid, teacherUid);
try {
TSRelationship relationship = tsRelationshipService.selectByStudentAndTeacherUid(studentUid, teacherUid);
if (relationship != null) {
return Result.success(relationship);
} else {
return Result.error("Relationship not found");
}
} catch (Exception e) {
LOGGER.error("Error checking relationship", e);
return Result.error("Internal server error");
}
}
/**
*
*/
@PostMapping("/save")
public Result<String> saveRelationship(@RequestBody TSRelationship tsRelationship) {
LOGGER.info("保存师生关系: 学生{} 和老师{}", tsRelationship.getStudentUid(), tsRelationship.getTeacherUid());
try {
boolean success = tsRelationshipService.saveRelationship(tsRelationship);
if (success) {
LOGGER.info("成功保存师生关系: 学生{} 和老师{}", tsRelationship.getStudentUid(), tsRelationship.getTeacherUid());
return Result.success("Save successful");
} else {
return Result.error("Save failed");
}
} catch (Exception e) {
LOGGER.error("Error saving relationship", e);
return Result.error("Internal server error");
}
}
/**
*
*/
@DeleteMapping("/delete")
public Result<String> deleteRelationship(@RequestParam Long studentUid, @RequestParam Long teacherUid) {
LOGGER.info("删除师生关系: 学生{} 和老师{}", studentUid, teacherUid);
try {
boolean success = tsRelationshipService.deleteRelationship(studentUid, teacherUid);
if (success) {
LOGGER.info("成功删除师生关系: 学生{} 和老师{}", studentUid, teacherUid);
return Result.success("Delete successful");
} else {
return Result.error("Delete failed or relationship not found");
}
} catch (Exception e) {
LOGGER.error("Error deleting relationship", e);
return Result.error("Internal server error");
}
}
}

@ -0,0 +1,462 @@
package com.tutoring_software.backend.uid_management.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.tutoring_software.backend.uid_management.Result;
import com.tutoring_software.backend.uid_management.entity.Teacher;
import com.tutoring_software.backend.uid_management.entity.TeacherCourse;
import com.tutoring_software.backend.uid_management.service.TeacherCourseService;
import com.tutoring_software.backend.uid_management.service.TeacherService;
@RestController
@RequestMapping("/api/teacher_info")
public class TeacherInfoController {
private static final Logger LOGGER = LoggerFactory.getLogger(TeacherInfoController.class);
@Autowired
private TeacherService teacherService;
@Autowired
private TeacherCourseService teacherCourseService;
/**
*
*
* @param request
* @return
*/
@PostMapping("/search")
public Result<List<TeacherInfo>> searchTeachers(@RequestBody Map<String, Object> request) {
// 用来存储老师信息的列表
List<TeacherInfo> result = new ArrayList<>();
try {
LOGGER.info("开始根据条件查询老师信息: {}", request);
// 1. 提取查询条件 - 修改subjects的处理逻辑确保正确接收Map<String,String>类型
Map<String, String> subjects = new HashMap<>();
if (request.get("subjects") instanceof Map) {
subjects = (Map<String, String>) request.get("subjects");
}
Double minRating = 0.0;
if (request.get("minRating") != null) {
try {
minRating = Double.parseDouble(request.get("minRating").toString());
} catch (NumberFormatException e) {
LOGGER.warn("评分格式无效: {}", request.get("minRating"));
}
}
Integer minComments = 0;
if (request.get("minComments") != null) {
try {
minComments = Integer.parseInt(request.get("minComments").toString());
} catch (NumberFormatException e) {
LOGGER.warn("评论数格式无效: {}", request.get("minComments"));
}
}
Integer maxComments = null;
if (request.get("maxComments") != null) {
try {
maxComments = Integer.parseInt(request.get("maxComments").toString());
} catch (NumberFormatException e) {
LOGGER.warn("最大评论数格式无效: {}", request.get("maxComments"));
}
}
// 2. 获取符合条件的老师列表
List<Teacher> filteredTeachers = new ArrayList<>();
// 根据评分和评论数阈值过滤
if (minRating != null && minComments != null) {
// 先按评分过滤,再对结果按评论数过滤
List<Teacher> ratingFiltered = teacherService.getByRatingGreaterThan(minRating);
if (maxComments == null) {
for (Teacher teacher : ratingFiltered) {
if (teacher.getComments() >= minComments) {
filteredTeachers.add(teacher);
}
}
} else {
for (Teacher teacher : ratingFiltered) {
if (teacher.getComments() >= minComments && teacher.getComments() <= maxComments) {
filteredTeachers.add(teacher);
}
}
}
} else if (minRating != null && maxComments != null) {
filteredTeachers = teacherService.getByCommentsBetween(minComments, maxComments);
} else {
filteredTeachers = teacherService.getByRatingGreaterThan(minRating);
}
// 3. 根据教学科目进一步过滤
List<Teacher> finalFilteredTeachers = new ArrayList<>();
if (!subjects.isEmpty()) {
for (Teacher teacher : filteredTeachers) {
TeacherCourse teacherCourse = teacherCourseService.getByTeacherUid(teacher.getTeacherUid());
if (teacherCourse != null && subjects.containsValue(teacherCourse.getSubject())) {
finalFilteredTeachers.add(teacher);
}
}
} else {
finalFilteredTeachers = filteredTeachers;
}
// 4. 组装返回数据
for (Teacher teacher : finalFilteredTeachers) {
// 处理老师可能教授多个学科的情况,确保每个老师只出现一次
boolean teacherExists = false;
TeacherInfo existingTeacherInfo = null;
// 查找是否已存在该老师
for (TeacherInfo ti : result) {
if (ti.getTeacherUid().equals(teacher.getTeacherUid())) {
teacherExists = true;
existingTeacherInfo = ti;
break;
}
}
// 获取当前老师的学科信息
TeacherCourse teacherCourse = teacherCourseService.getByTeacherUid(teacher.getTeacherUid());
String subject = (teacherCourse != null) ? teacherCourse.getSubject() : null;
if (teacherExists && existingTeacherInfo != null) {
// 老师已存在,检查是否需要添加新学科
List<String> existingSubjects = existingTeacherInfo.getSubjects();
if (subject != null && !existingSubjects.contains(subject)) {
existingSubjects.add(subject);
// 更新学科匹配数量
if (subjects.containsValue(subject)) {
existingTeacherInfo.setSubjectMatchCount(existingTeacherInfo.getSubjectMatchCount() + 1);
}
}
} else {
// 老师不存在创建新的TeacherInfo对象
TeacherInfo newTeacherInfo = new TeacherInfo();
newTeacherInfo.setTeacherUid(teacher.getTeacherUid());
newTeacherInfo.setRating(teacher.getRating());
newTeacherInfo.setComments(teacher.getComments());
// 初始化学科列表并添加当前学科
List<String> teacherSubjects = new ArrayList<>();
if (subject != null) {
teacherSubjects.add(subject);
}
newTeacherInfo.setSubjects(teacherSubjects);
// 设置科目匹配数量
int matchCount = (subject != null && subjects.containsValue(subject)) ? 1 : 0;
newTeacherInfo.setSubjectMatchCount(matchCount);
result.add(newTeacherInfo);
}
}
LOGGER.info("查询完成,共找到{}位符合条件的老师", result.size());
return Result.success(result);
} catch (Exception e) {
LOGGER.error("查询老师信息失败", e);
return Result.error("查询老师信息失败:" + e.getMessage());
}
}
@PostMapping("/register")
public Result<String> registerTeacher(@RequestBody Map<String, Object> request) {
try {
LOGGER.info("开始存储老师注册信息: {}", request);
// 1. 提取请求参数
Long teacherUid = null;
// String subjectCode = null;
Map<String, String> subjects = new HashMap<>();
if (request.get("teacherUid") != null) {
try {
teacherUid = Long.parseLong(request.get("teacherUid").toString());
} catch (NumberFormatException e) {
LOGGER.error("老师UID格式无效: {}", request.get("teacherUid"));
return Result.error("老师UID格式无效");
}
}
// 修改为处理Map类型的subjects
if (request.get("subjects") != null) {
try {
// 假设传入的subjects是一个Map<String, String>其中key为学科IDvalue为学科名称
Object subjectsObj = request.get("subjects");
if (subjectsObj instanceof Map) {
subjects = (Map<String, String>) subjectsObj;
} else {
LOGGER.error("学科信息格式不正确应为Map类型");
return Result.error("学科信息格式不正确应为Map类型");
}
} catch (Exception e) {
LOGGER.error("解析学科信息失败: {}", e.getMessage());
return Result.error("解析学科信息失败");
}
}
// 2. 参数校验
if (teacherUid == null) {
LOGGER.error("老师UID不能为空");
return Result.error("老师UID不能为空");
}
if (subjects.isEmpty()) {
LOGGER.error("至少需要选择一个学科");
return Result.error("至少需要选择一个学科");
}
// 3. 存储老师基本信息到teachers表只需要存储一次
// 保存老师信息teacherUid, 初始评分0.0, 初始评论数0
boolean teacherSaved = teacherService.saveTeacher(teacherUid, 0.0, 0);
if (!teacherSaved) {
LOGGER.error("保存老师基本信息失败teacherUid: {}", teacherUid);
return Result.error("保存老师基本信息失败");
}
// 4. 遍历所有学科存储老师课程信息到teachers_courses表
boolean allCoursesSaved = true;
StringBuilder failedSubjects = new StringBuilder();
for (Map.Entry<String, String> entry : subjects.entrySet()) {
String subjectCode = entry.getKey();
boolean courseSaved = teacherCourseService.saveTeacherCourse(teacherUid, subjectCode);
if (!courseSaved) {
allCoursesSaved = false;
failedSubjects.append(subjectCode).append(", ");
LOGGER.error("保存老师课程信息失败teacherUid: {}, subjectCode: {}", teacherUid, subjectCode);
}
}
if (!allCoursesSaved) {
LOGGER.error("部分课程信息保存失败,失败的学科代码: {}", failedSubjects.toString());
return Result.error("部分课程信息保存失败: " + failedSubjects.toString());
}
LOGGER.info("老师注册信息存储成功teacherUid: {}, 学科数量: {}", teacherUid, subjects.size());
return Result.success("老师注册信息存储成功");
} catch (Exception e) {
LOGGER.error("存储老师注册信息失败", e);
return Result.error("存储老师注册信息失败:" + e.getMessage());
}
}
// 当老师修改了自己的教学选课信息的时候要更新teachers_courses表
/**
*
*
* @param request UID
* @return
*/
@PostMapping("/updateCourses")
public Result<String> updateTeacherCourses(@RequestBody Map<String, Object> request) {
try {
LOGGER.info("开始更新老师课程信息: {}", request);
// 1. 提取请求参数
Long teacherUid = null;
Map<String, String> subjects = new HashMap<>();
if (request.get("teacherUid") != null) {
try {
teacherUid = Long.parseLong(request.get("teacherUid").toString());
} catch (NumberFormatException e) {
LOGGER.error("老师UID格式无效: {}", request.get("teacherUid"));
return Result.error("老师UID格式无效");
}
}
// 获取学科信息
if (request.get("subjects") != null) {
try {
Object subjectsObj = request.get("subjects");
if (subjectsObj instanceof Map) {
subjects = (Map<String, String>) subjectsObj;
} else {
LOGGER.error("学科信息格式不正确应为Map类型");
return Result.error("学科信息格式不正确应为Map类型");
}
} catch (Exception e) {
LOGGER.error("解析学科信息失败: {}", e.getMessage());
return Result.error("解析学科信息失败");
}
}
// 2. 参数校验
if (teacherUid == null) {
LOGGER.error("老师UID不能为空");
return Result.error("老师UID不能为空");
}
if (subjects.isEmpty()) {
LOGGER.error("至少需要选择一个学科");
return Result.error("至少需要选择一个学科");
}
// 3. 先删除老师现有的所有课程信息
boolean coursesDeleted = teacherCourseService.deleteTeacherCourse(teacherUid);
if (!coursesDeleted) {
LOGGER.error("删除老师原有课程信息失败teacherUid: {}", teacherUid);
// 即使删除失败也继续尝试添加新课程,因为可能是因为课程不存在
}
// 4. 遍历所有学科,更新老师课程信息
boolean allCoursesUpdated = true;
StringBuilder failedSubjects = new StringBuilder();
for (Map.Entry<String, String> entry : subjects.entrySet()) {
String subjectCode = entry.getKey();
boolean courseSaved = teacherCourseService.saveTeacherCourse(teacherUid, subjectCode);
if (!courseSaved) {
allCoursesUpdated = false;
failedSubjects.append(subjectCode).append(", ");
LOGGER.error("更新老师课程信息失败teacherUid: {}, subjectCode: {}", teacherUid, subjectCode);
}
}
if (!allCoursesUpdated) {
LOGGER.error("部分课程信息更新失败,失败的学科代码: {}", failedSubjects.toString());
return Result.error("部分课程信息更新失败: " + failedSubjects.toString());
}
LOGGER.info("老师课程信息更新成功teacherUid: {}, 学科数量: {}", teacherUid, subjects.size());
return Result.success("老师课程信息更新成功");
} catch (Exception e) {
LOGGER.error("更新老师课程信息失败", e);
return Result.error("更新老师课程信息失败:" + e.getMessage());
}
}
// 删除老师信息
/**
*
*
* @param request UID
* @return
*/
@DeleteMapping("/delete")
public Result<String> deleteTeacher(@RequestBody Map<String, Object> request) {
try {
LOGGER.info("开始删除老师信息: {}", request);
// 1. 提取请求参数
Long teacherUid = null;
if (request.get("teacherUid") != null) {
try {
teacherUid = Long.parseLong(request.get("teacherUid").toString());
} catch (NumberFormatException e) {
LOGGER.error("老师UID格式无效: {}", request.get("teacherUid"));
return Result.error("老师UID格式无效");
}
}
// 2. 参数校验
if (teacherUid == null) {
LOGGER.error("老师UID不能为空");
return Result.error("老师UID不能为空");
}
// 3. 先删除老师的课程信息
teacherCourseService.deleteTeacherCourse(teacherUid);
// 即使课程信息不存在,也继续删除老师基本信息
// 4. 再删除老师基本信息
boolean teacherDeleted = teacherService.deleteTeacher(teacherUid);
if (teacherDeleted) {
LOGGER.info("删除老师信息成功teacherUid: {}", teacherUid);
return Result.success("删除老师信息成功");
} else {
LOGGER.error("删除老师基本信息失败teacherUid: {}", teacherUid);
return Result.error("删除老师信息失败");
}
} catch (Exception e) {
LOGGER.error("删除老师信息异常", e);
return Result.error("删除老师信息失败:" + e.getMessage());
}
}
// 老师信息的存储
// @PostMapping("/save")
/**
*
*/
public static class TeacherInfo {
private Long teacherUid; // 老师uid
private double rating = 0.0; // 老师评分
private int comments = 0; // 老师评价数量
private List<String> subjects = new ArrayList<>(); // 老师选择的学科
private int subjectMatchCount = 0; // 老师选择学科对口数量
// 无参构造函数
public TeacherInfo() {
}
// 全参构造函数
public TeacherInfo(Long teacherUid, double rating, int comments, List<String> subjects, int subjectMatchCount) {
this.teacherUid = teacherUid;
this.rating = rating;
this.comments = comments;
this.subjects = subjects;
this.subjectMatchCount = subjectMatchCount;
}
// getter和setter方法
public Long getTeacherUid() {
return teacherUid;
}
public void setTeacherUid(Long teacherUid) {
this.teacherUid = teacherUid;
}
public double getRating() {
return rating;
}
public void setRating(double rating) {
this.rating = rating;
}
public int getComments() {
return comments;
}
public void setComments(int comments) {
this.comments = comments;
}
public List<String> getSubjects() {
return subjects;
}
public void setSubjects(List<String> subjects) {
this.subjects = subjects;
}
public int getSubjectMatchCount() {
return subjectMatchCount;
}
public void setSubjectMatchCount(int subjectMatchCount) {
this.subjectMatchCount = subjectMatchCount;
}
}
}

@ -0,0 +1,82 @@
package com.tutoring_software.backend.uid_management.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.tutoring_software.backend.uid_management.Result;
import com.tutoring_software.backend.uid_management.entity.UserAvatars;
import com.tutoring_software.backend.uid_management.service.UserAvatarsService;
@RestController
@RequestMapping("/api/user-avatars")
public class UserAvatarsController {
private static final Logger LOGGER = LoggerFactory.getLogger(UserAvatarsController.class);
@Autowired
private UserAvatarsService userAvatarsService;
@GetMapping("/get/{uid}")
public Result<UserAvatars> getByUid(@PathVariable String uid) {
// 把uid转换为Long类型
Long uidLong = Long.parseLong(uid);
LOGGER.info("需要查询的uid为:{}", uidLong);
try {
UserAvatars result = userAvatarsService.getByUid(uidLong);
if (result != null) {
return Result.success(result);
} else {
return Result.error("UID not found");
}
} catch (Exception e) {
LOGGER.error("Error getting by UID", e);
return Result.error("Internal server error");
}
}
// 存储头像数据
@PostMapping("/save")
public Result<String> saveUserAvatars(@RequestBody UserAvatars userAvatars) {
try {
// 首先检查用户是否已存在
UserAvatars existingUser = userAvatarsService.getByUid(userAvatars.getUid());
if (existingUser != null) {
LOGGER.error("用户{}已存在,保存失败", userAvatars.getUid());
return Result.error("User already exists"); // 返回明确的用户已存在错误
}
boolean success = userAvatarsService.saveUserAvatars(
userAvatars.getUid(), userAvatars.getImageData(),
userAvatars.getMimeType());
if (success) {
LOGGER.info("成功存储数据:uid={}", userAvatars.getUid());
return Result.success("Save successful");
} else {
return Result.error("Save failed");
}
} catch (Exception e) {
LOGGER.error("Error saving user avatar", e);
return Result.error("Internal server error");
}
}
// 更新头像数据
@PutMapping("/update")
public Result<String> updateUserAvatars(@RequestBody UserAvatars userAvatars) {
try {
boolean success = userAvatarsService.updateUserAvatars(
userAvatars.getUid(), userAvatars.getImageData(),
userAvatars.getMimeType());
if (success) {
LOGGER.info("成功更新用户{}的头像", userAvatars.getUid());
return Result.success("Update successful");
} else {
LOGGER.warn("更新用户{}头像失败,用户不存在", userAvatars.getUid());
return Result.error("User not found");
}
} catch (Exception e) {
LOGGER.error("Error updating user avatar", e);
return Result.error("Internal server error");
}
}
}

@ -0,0 +1,95 @@
package com.tutoring_software.backend.uid_management.controller;
import com.tutoring_software.backend.uid_management.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.tutoring_software.backend.uid_management.entity.UserDataItem;
import com.tutoring_software.backend.uid_management.service.UserDataItemService;
@RestController
@RequestMapping("/api/user-data-item")
public class UserDataItemController {
private static final Logger LOGGER = LoggerFactory.getLogger(UserDataItemController.class);
@Autowired
private UserDataItemService userDataItemService;
@GetMapping("/get/{uid}")
public Result<UserDataItem> getByUid(@PathVariable String uid) {
// 把uid转换为Long类型
Long uidLong = Long.parseLong(uid);
LOGGER.info("需要查询的uid为:{}", uidLong);
try {
UserDataItem result = userDataItemService.getByUid(uidLong);
if (result != null) {
return Result.success(result);
} else {
return Result.error("UID not found");
}
} catch (Exception e) {
LOGGER.error("Error getting by UID", e);
return Result.error("Internal server error");
}
}
@PostMapping("/save")
public Result<String> saveUserDataItem(@RequestBody UserDataItem userDataItem) {
try {
// 首先检查用户是否已存在
UserDataItem existingUser = userDataItemService.getByUid(userDataItem.getUid());
if (existingUser != null) {
LOGGER.error("用户{}已存在,保存失败", userDataItem.getUid());
return Result.error("User already exists"); // 返回明确的用户已存在错误
}
boolean success = userDataItemService.saveUserDataItem(
userDataItem.getUid(), userDataItem.getUsername(),
userDataItem.getPhoneNumber(), userDataItem.getEmail(),
userDataItem.getBirthday(), userDataItem.getRole(),
userDataItem.getGender(), userDataItem.getPassword(),
userDataItem.getTeachingSubjects(), userDataItem.getLearningSubjects());
if (success) {
LOGGER.info("成功存储数据:uid={},username={}", userDataItem.getUid(), userDataItem.getUsername());
return Result.success("Save successful");
} else {
return Result.error("Save failed");
}
} catch (Exception e) {
LOGGER.error("Error saving user data item", e);
return Result.error("Internal server error");
}
}
@PutMapping("/update")
public Result<String> updateUserDataItem(@RequestBody UserDataItem userDataItem) {
try {
// 检查用户是否存在
UserDataItem existingUser = userDataItemService.getByUid(userDataItem.getUid());
if (existingUser == null) {
LOGGER.error("用户{}不存在,更新失败", userDataItem.getUid());
return Result.error("User not found");
}
boolean success = userDataItemService.updateUserDataItem(
userDataItem.getUid(), userDataItem.getUsername(),
userDataItem.getPhoneNumber(), userDataItem.getEmail(),
userDataItem.getBirthday(), userDataItem.getRole(),
userDataItem.getGender(), userDataItem.getPassword(),
userDataItem.getTeachingSubjects(), userDataItem.getLearningSubjects());
if (success) {
LOGGER.info("成功更新用户数据:uid={},username={}", userDataItem.getUid(), userDataItem.getUsername());
return Result.success("Update successful");
} else {
return Result.error("Update failed");
}
} catch (Exception e) {
LOGGER.error("Error updating user data item", e);
return Result.error("Internal server error");
}
}
}

@ -0,0 +1,81 @@
package com.tutoring_software.backend.uid_management.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.tutoring_software.backend.uid_management.Result;
import com.tutoring_software.backend.uid_management.entity.UserSignature;
import com.tutoring_software.backend.uid_management.service.UserSignatureService;
@RestController
@RequestMapping("/api/user-signatures")
public class UserSignatureController {
private static final Logger LOGGER = LoggerFactory.getLogger(UserSignatureController.class);
@Autowired
private UserSignatureService userSignatureService;
// 通过uid获取用户的签名
@GetMapping("/get/{uid}")
public Result<UserSignature> getByUid(@PathVariable String uid) {
// 把uid转换为Long类型
Long uidLong = Long.parseLong(uid);
LOGGER.info("需要查询的uid为:{}", uidLong);
try {
UserSignature result = userSignatureService.getByUid(uidLong);
if (result != null) {
return Result.success(result);
} else {
return Result.error("UID not found");
}
} catch (Exception e) {
LOGGER.error("Error getting by UID", e);
return Result.error("Internal server error");
}
}
// 存储用户签名数据
@PostMapping("/save")
public Result<String> saveUserSignature(@RequestBody UserSignature userSignature) {
try {
// 首先检查用户是否已存在
UserSignature existingUser = userSignatureService.getByUid(userSignature.getUid());
if (existingUser != null) {
LOGGER.error("用户{}已存在,保存失败", userSignature.getUid());
return Result.error("User already exists"); // 返回明确的用户已存在错误
}
boolean success = userSignatureService.saveUserSignature(
userSignature.getUid(), userSignature.getSignatures());
if (success) {
LOGGER.info("成功存储数据:uid={}", userSignature.getUid());
return Result.success("Save successful");
} else {
return Result.error("Save failed");
}
} catch (Exception e) {
LOGGER.error("Error saving user signature", e);
return Result.error("Internal server error");
}
}
// 更新签名数据
@PutMapping("/update")
public Result<String> updateUserSignatures(@RequestBody UserSignature userSignature) {
try {
boolean success = userSignatureService.updateSignature(
userSignature.getUid(), userSignature.getSignatures());
if (success) {
LOGGER.info("成功更新用户{}的签名", userSignature.getUid());
return Result.success("Update successful");
} else {
LOGGER.warn("更新用户{}签名失败,用户不存在", userSignature.getUid());
return Result.error("User not found");
}
} catch (Exception e) {
LOGGER.error("Error updating user signature", e);
return Result.error("Internal server error");
}
}
}

@ -0,0 +1,94 @@
package com.tutoring_software.backend.uid_management.entity;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
@TableName("comments")
public class CommentItem {
//BIGSERVAL
@TableId(type=IdType.AUTO)
private Long id;
/*字段*/
//学生UID
//老师UID
@TableField("student_uid")
private Long studentUid;
@TableField("teacher_uid")
private Long teacherUid;
//评论的内容 VarChar(1000)
//评分 Char(1)
//评论的时间 TIMESTAMP、
//评论的学科VarChar(40)
@TableField("content")
private String content;
@TableField("rating")
private String rating;
@TableField("created_at")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdAt;
@TableField("subject")
private String subject;
//构造函数
public CommentItem() {
}
public CommentItem(Long studentUid, Long teacherUid, String content, String rating, LocalDateTime createdAt, String subject) {
this.studentUid = studentUid;
this.teacherUid = teacherUid;
this.content = content;
this.rating = rating;
this.createdAt = createdAt;
this.subject = subject;
}
//getter和setter
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getStudentUid() {
return studentUid;
}
public void setStudentUid(Long studentUid) {
this.studentUid = studentUid;
}
public Long getTeacherUid() {
return teacherUid;
}
public void setTeacherUid(Long teacherUid) {
this.teacherUid = teacherUid;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getRating() {
return rating;
}
public void setRating(String rating) {
this.rating = rating;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
}

@ -0,0 +1,49 @@
package com.tutoring_software.backend.uid_management.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@TableName("email_id")
public class EmailUid {
@TableId(type=IdType.AUTO)
private Long id;
//字段
@TableField("email")
private String email;
@TableField("uid") // UID字段
private Long uid;
//构造函数
public EmailUid() {}
public EmailUid(String email, Long uid){
this.email = email;
this.uid = uid;
}
//getter和setter
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Long getUid() {
return uid;
}
public void setUid(Long uid) {
this.uid = uid;
}
}

@ -0,0 +1,49 @@
package com.tutoring_software.backend.uid_management.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@TableName("phone_number_id")
public class PhoneNumberUid {
//主键字段
@TableId(type = IdType.AUTO)
private Long id;
//字段
@TableField("phone_number") // 手机号字段
private String phoneNumber;
@TableField("uid") // UID字段
private Long uid;
// 构造函数
public PhoneNumberUid() {}
public PhoneNumberUid(String phoneNumber, Long uid){
this.phoneNumber = phoneNumber;
this.uid = uid;
}
//getter和setter
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public Long getUid() {
return uid;
}
public void setUid(Long uid) {
this.uid = uid;
}
}

@ -0,0 +1,78 @@
package com.tutoring_software.backend.uid_management.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@TableName("teacher_student_relationship")
public class TSRelationship {
// 主键字段 - BIGSERIAL类型
@TableId(type = IdType.AUTO)
private Long id;
// 学生UID字段
@TableField("student_uid")
private Long studentUid;
// 老师UID字段
@TableField("teacher_uid")
private Long teacherUid;
//学科选择关系char(6)
@TableField("subject")
private int subject;
//学情分析
@TableField("analysis")
private String analysis;
// 构造函数
public TSRelationship() {}
public TSRelationship(Long studentUid, Long teacherUid) {
this.studentUid = studentUid;
this.teacherUid = teacherUid;
}
// getter和setter方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getStudentUid() {
return studentUid;
}
public void setStudentUid(Long studentUid) {
this.studentUid = studentUid;
}
public Long getTeacherUid() {
return teacherUid;
}
public void setTeacherUid(Long teacherUid) {
this.teacherUid = teacherUid;
}
public int getSubject() {
return subject;
}
public void setSubject(int subject) {
this.subject = subject;
}
public String getAnalysis() {
return analysis;
}
public void setAnalysis(String analysis) {
this.analysis = analysis;
}
}

@ -0,0 +1,69 @@
package com.tutoring_software.backend.uid_management.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@TableName("teachers")
public class Teacher {
// BIGSERIAL 自增id
@TableId(type = IdType.AUTO)
private Long id;
// 老师的uid
@TableField("teacher_uid")
private Long teacherUid;
// 老师的评分rating
@TableField("rating")
private double rating;
// 老师的评价数量 int comments
@TableField("comments")
private int comments;
// 无参构造函数
public Teacher() {
}
// 全参构造函数
public Teacher(Long teacherUid, double rating, int comments) {
this.teacherUid = teacherUid;
this.rating = rating;
this.comments = comments;
}
// getter 和 setter 方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getTeacherUid() {
return teacherUid;
}
public void setTeacherUid(Long teacherUid) {
this.teacherUid = teacherUid;
}
public double getRating() {
return rating;
}
public void setRating(double rating) {
this.rating = rating;
}
public int getComments() {
return comments;
}
public void setComments(int comments) {
this.comments = comments;
}
}

@ -0,0 +1,56 @@
package com.tutoring_software.backend.uid_management.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@TableName("teachers_courses")
public class TeacherCourse {
// BIGSERIAL 自增id
@TableId(type = IdType.AUTO)
private Long id;
// 老师的uid
@TableField("teacher_uid")
private Long teacherUid;
// 老师选择教学的学科(6位整数组成的学科ID)
@TableField("subject")
private String subject;
// 无参构造函数
public TeacherCourse() {
}
// 全参构造函数
public TeacherCourse(Long teacherUid, String subject) {
this.teacherUid = teacherUid;
this.subject = subject;
}
// getter 和 setter 方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getTeacherUid() {
return teacherUid;
}
public void setTeacherUid(Long teacherUid) {
this.teacherUid = teacherUid;
}
public String getSubject(){
return subject;
}
public void setSubject(String subject){
this.subject = subject;
}
}

@ -0,0 +1,75 @@
package com.tutoring_software.backend.uid_management.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
@TableName("user_avatars")
public class UserAvatars {
// 主键字段
@TableId(type = IdType.AUTO)
private Long id;
// 关联用户IDNOT NULL约束
@TableField("uid")
private Long uid;
// 图片二进制数据NOT NULL约束
@TableField("image_data")
private byte[] imageData;
// 图片类型,如 'image/jpeg'
@TableField("mime_type")
private String mimeType;
// 创建时间,默认值为当前时间戳
@TableField("created_at")
private LocalDateTime createdAt;
//构造方法
public UserAvatars(){}
public UserAvatars(Long uid, byte[] imageData, String mimeType) {
this.uid = uid;
this.imageData = imageData;
this.mimeType = mimeType;
}
// Getter和Setter方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getUid() {
return uid;
}
public void setUid(Long uid) {
this.uid = uid;
}
public byte[] getImageData() {
return imageData;
}
public void setImageData(byte[] imageData) {
this.imageData = imageData;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
}

@ -0,0 +1,130 @@
package com.tutoring_software.backend.uid_management.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@TableName("user_data_item")
public class UserDataItem {
//主键字段
@TableId(type = IdType.AUTO)
private Long id;
//字段:分别为
// uid BIGINT 不能为空
// 用户名 varchar(100) 不能为空
// 手机号 varchar(20) 可以为空
// 邮箱 varchar(255) 可以为空
// 生日 varchar(20) 可以为空
// 身份 varchar(15) 不可为空
//性别 varchar(10) 不可为空
// 密码 varchar(40) 不可为空
// 教学科目 varchar(1000) 可以为空
// 学习科目 varchar(1000) 可以为空
@TableField("uid")
private Long uid;
@TableField("username")
private String username;
@TableField("phone_number")
private String phoneNumber = "";
@TableField("email")
private String email = "";
@TableField("birthday")
private String birthday = "";
@TableField("role")
private String role;
@TableField("gender")
private String gender = "隐藏";
@TableField("password")
private String password;
@TableField("teaching_subjects")
private String teachingSubjects ;
@TableField("learning_subjects")
private String learningSubjects ;
public UserDataItem(){}
public UserDataItem(
Long uid, String username, String phoneNumber,
String email, String birthday, String role,
String gender, String password,
String teachingSubjects, String learningSubjects) {
this.uid = uid;
this.username = username;
this.phoneNumber = phoneNumber;
this.email = email;
this.birthday = birthday;
this.role = role;
this.gender = gender;
this.password = password;
this.teachingSubjects = teachingSubjects;
this.learningSubjects = learningSubjects;
}
//getter 和 setter
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getUid() {
return uid;
}
public void setUid(Long uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getTeachingSubjects() {
return teachingSubjects;
}
public void setTeachingSubjects(String teachingSubjects) {
this.teachingSubjects = teachingSubjects;
}
public String getLearningSubjects() {
return learningSubjects;
}
public void setLearningSubjects(String learningSubjects) {
this.learningSubjects = learningSubjects;
}
}

@ -0,0 +1,46 @@
package com.tutoring_software.backend.uid_management.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@TableName("user_signature")
public class UserSignature {
// 主键字段
@TableId(type = IdType.AUTO)
private Long id;
// 关联用户IDNOT NULL约束
@TableField("uid")
private Long uid;
//用户的签名
@TableField("signatures")
private String signatures;
//构造方法
public UserSignature(){}
public UserSignature(Long uid, String signatures) {
this.uid = uid;
this.signatures = signatures;
}
//getter和setter
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getUid() {
return uid;
}
public void setUid(Long uid) {
this.uid = uid;
}
public String getSignatures() {
return signatures;
}
public void setSignatures(String signatures) {
this.signatures = signatures;
}
}

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

Loading…
Cancel
Save