Initial commit: 课程任务进度管理系统
This commit is contained in:
commit
2efddcfe2e
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/mvnw text eol=lf
|
||||||
|
*.cmd text eol=crlf
|
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@ -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/
|
19
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
19
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# 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.
|
||||||
|
wrapperVersion=3.3.2
|
||||||
|
distributionType=only-script
|
||||||
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
|
411
API文档-新.md
Normal file
411
API文档-新.md
Normal file
@ -0,0 +1,411 @@
|
|||||||
|
# 进度管理系统 API 文档
|
||||||
|
|
||||||
|
## 基础信息
|
||||||
|
|
||||||
|
- 基础 URL: `http://localhost:1218`
|
||||||
|
- 所有请求和响应均使用 JSON 格式
|
||||||
|
- 所有需要认证的接口都需要在请求头中携带 `Authorization: Bearer {token}`
|
||||||
|
|
||||||
|
## 通用响应格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 10000, // 响应码
|
||||||
|
"message": "成功", // 响应消息
|
||||||
|
"data": {} // 响应数据
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应码说明
|
||||||
|
|
||||||
|
| 响应码 | 说明 | 消息 |
|
||||||
|
| ------ | ---------- | ---------- |
|
||||||
|
| 10000 | 成功 | 成功 |
|
||||||
|
| 10001 | 参数错误 | 参数无效 |
|
||||||
|
| 10002 | 邮箱重复 | 邮箱已存在 |
|
||||||
|
| 10003 | 用户不存在 | 用户不存在 |
|
||||||
|
| 10004 | 密码错误 | 密码错误 |
|
||||||
|
| 10005 | 邮箱已存在 | 邮箱已存在 |
|
||||||
|
| 10006 | 未授权 | 未授权 |
|
||||||
|
| 10007 | 令牌过期 | 令牌已过期 |
|
||||||
|
| 10008 | 令牌无效 | 无效的令牌 |
|
||||||
|
| 10009 | 系统错误 | 系统错误 |
|
||||||
|
|
||||||
|
## 字段说明
|
||||||
|
|
||||||
|
### 用户相关字段
|
||||||
|
|
||||||
|
1. **角色 (roles)**
|
||||||
|
|
||||||
|
- 1: 教师
|
||||||
|
- 2: 普通管理员
|
||||||
|
- 3: 沟通联络人
|
||||||
|
- 4: 系统管理员
|
||||||
|
|
||||||
|
2. **岗位 (jobs)**
|
||||||
|
|
||||||
|
- 1: 课程制作教师
|
||||||
|
- 2: 课程购买方项目负责人
|
||||||
|
- 3: 课程制作方沟通联络人
|
||||||
|
- 4: 系统制作方项目负责人
|
||||||
|
|
||||||
|
3. **用户状态 (status)**
|
||||||
|
- 1: 正常
|
||||||
|
- 0: 禁用
|
||||||
|
|
||||||
|
### 课程任务相关字段
|
||||||
|
|
||||||
|
1. **进度状态 (progressStatus)**
|
||||||
|
- 0: 脚本上传
|
||||||
|
- 1: 脚本确认
|
||||||
|
- 2: 视频拍摄
|
||||||
|
- 3: 后期制作
|
||||||
|
- 4: 任务完成
|
||||||
|
|
||||||
|
## 用户接口
|
||||||
|
|
||||||
|
### 1. 用户注册
|
||||||
|
|
||||||
|
- **接口**:`POST /api/users`
|
||||||
|
- **描述**:创建新用户
|
||||||
|
- **认证**:不需要
|
||||||
|
- **请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "testuser", // 用户名,不可为空
|
||||||
|
"email": "test@example.com", // 邮箱,不可为空且唯一
|
||||||
|
"password": "password123", // 密码,不可为空
|
||||||
|
"departmentId": 1, // 部门ID,不可为空,关联departments表
|
||||||
|
"roles": 1, // 角色:1-教师,2-普通管理员,3-沟通联络人,4-系统管理员
|
||||||
|
"jobs": 1, // 岗位:1-课程制作教师,2-课程购买方项目负责人,3-课程制作方沟通联络人,4-系统制作方项目负责人
|
||||||
|
"avatar": "http://example.com/avatar.jpg", // 头像URL,可选
|
||||||
|
"creatorId": 1 // 创建者ID,不可为空
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **成功响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 10000,
|
||||||
|
"message": "成功",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **错误响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 10005,
|
||||||
|
"message": "邮箱已存在",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 用户登录
|
||||||
|
|
||||||
|
- **接口**:`POST /api/users/login`
|
||||||
|
- **描述**:用户登录获取 token
|
||||||
|
- **认证**:不需要
|
||||||
|
- **请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"email": "test@example.com", // 邮箱
|
||||||
|
"password": "password123", // 密码
|
||||||
|
"remember": true // 是否记住登录(可选)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **成功响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 10000,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"token": "eyJhbGciOiJIUzM4NCJ9..." // JWT令牌
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 用户登出
|
||||||
|
|
||||||
|
- **接口**:`POST /api/users/logout`
|
||||||
|
- **描述**:用户登出,使当前 token 失效
|
||||||
|
- **认证**:需要
|
||||||
|
- **成功响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 10000,
|
||||||
|
"message": "成功",
|
||||||
|
"data": "登出成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 获取当前用户信息
|
||||||
|
|
||||||
|
- **接口**:`GET /api/users/current`
|
||||||
|
- **描述**:获取当前登录用户信息
|
||||||
|
- **认证**:需要
|
||||||
|
- **成功响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 10000,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"id": 12,
|
||||||
|
"username": "testuser",
|
||||||
|
"email": "test@example.com",
|
||||||
|
"departmentId": 1,
|
||||||
|
"roles": 1,
|
||||||
|
"jobs": 1,
|
||||||
|
"avatar": null,
|
||||||
|
"creatorId": 1,
|
||||||
|
"status": 1,
|
||||||
|
"createdAt": 1734498503690,
|
||||||
|
"updatedAt": 1734498503690,
|
||||||
|
"enabled": true,
|
||||||
|
"authorities": [
|
||||||
|
{
|
||||||
|
"authority": "ROLE_USER"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 获取用户列表
|
||||||
|
|
||||||
|
- **接口**:`GET /api/users/list`
|
||||||
|
- **描述**:分页获取用户列表
|
||||||
|
- **认证**:需要
|
||||||
|
- **查询参数**:
|
||||||
|
- `page`: 页码(从 1 开始)
|
||||||
|
- `limit`: 每页数量(默认 10)
|
||||||
|
- **成功响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 10000,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"username": "admin",
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"departmentId": 1,
|
||||||
|
"roles": 4,
|
||||||
|
"jobs": 4,
|
||||||
|
"avatar": null,
|
||||||
|
"creatorId": 1,
|
||||||
|
"status": 1,
|
||||||
|
"createdAt": 1734491549,
|
||||||
|
"updatedAt": 1734491549
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 12,
|
||||||
|
"currentPage": 1,
|
||||||
|
"pageSize": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 禁用用户
|
||||||
|
|
||||||
|
- **接口**:`POST /api/users/disable/{userId}`
|
||||||
|
- **描述**:禁用指定用户
|
||||||
|
- **认证**:需要
|
||||||
|
- **路径参数**:
|
||||||
|
- `userId`: 用户 ID
|
||||||
|
- **成功响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 10000,
|
||||||
|
"message": "成功",
|
||||||
|
"data": "用户已禁用"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 课程任务接口
|
||||||
|
|
||||||
|
### 1. 获取课程任务列表
|
||||||
|
|
||||||
|
- **接口**:`GET /api/lesson-tasks`
|
||||||
|
- **描述**:分页获取课程任务列表
|
||||||
|
- **认证**:需要
|
||||||
|
- **查询参数**:
|
||||||
|
- `page`: 页码(从 1 开始)
|
||||||
|
- `size`: 每页数量(默认 10)
|
||||||
|
- `userId`: 用户 ID(可选)
|
||||||
|
- **成功响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 10000,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"courseName": "Test Course",
|
||||||
|
"microLessonName": "Test Lesson",
|
||||||
|
"userId": 12,
|
||||||
|
"progressStatus": 1,
|
||||||
|
"scriptUploadTime": 1734498510000,
|
||||||
|
"scriptConfirmTime": 1734498510000,
|
||||||
|
"videoCaptureTime": 1734498510000,
|
||||||
|
"videoConfirmTime": 1734498510000,
|
||||||
|
"finishTime": 1734498510000,
|
||||||
|
"advise": "Test advice",
|
||||||
|
"createdAt": 1734498546322,
|
||||||
|
"updatedAt": 1734498546322
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"totalElements": 4,
|
||||||
|
"totalPages": 1,
|
||||||
|
"size": 10,
|
||||||
|
"number": 0,
|
||||||
|
"first": true,
|
||||||
|
"last": true,
|
||||||
|
"empty": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 获取单个课程任务
|
||||||
|
|
||||||
|
- **接口**:`GET /api/lesson-tasks/{id}`
|
||||||
|
- **描述**:获取指定 ID 的课程任务
|
||||||
|
- **认证**:需要
|
||||||
|
- **路径参数**:
|
||||||
|
- `id`: 课程任务 ID
|
||||||
|
- **成功响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 10000,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"id": 6,
|
||||||
|
"courseName": "Test Course",
|
||||||
|
"microLessonName": "Test Lesson",
|
||||||
|
"userId": 12,
|
||||||
|
"progressStatus": 1,
|
||||||
|
"scriptUploadTime": 1734498510000,
|
||||||
|
"scriptConfirmTime": 1734498510000,
|
||||||
|
"videoCaptureTime": 1734498510000,
|
||||||
|
"videoConfirmTime": 1734498510000,
|
||||||
|
"finishTime": 1734498510000,
|
||||||
|
"advise": "Test advice",
|
||||||
|
"createdAt": 1734498546322,
|
||||||
|
"updatedAt": 1734498546322
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 创建课程任务
|
||||||
|
|
||||||
|
- **接口**:`POST /api/lesson-tasks`
|
||||||
|
- **描述**:创建新的课程任务
|
||||||
|
- **认证**:需要
|
||||||
|
- **请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"courseName": "Java基础", // 课程名称,不可为空
|
||||||
|
"microLessonName": "Java变量", // 微课名称,不可为空
|
||||||
|
"userId": 1, // 负责人ID,不可为空,关联users表
|
||||||
|
"advise": "请注意讲解速度" // 任务建议或备注,可选
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **成功响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 10000,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"id": 6,
|
||||||
|
"courseName": "Java基础",
|
||||||
|
"microLessonName": "Java变量",
|
||||||
|
"userId": 1,
|
||||||
|
"progressStatus": 1,
|
||||||
|
"scriptUploadTime": ,
|
||||||
|
"scriptConfirmTime": ,
|
||||||
|
"videoCaptureTime": ,
|
||||||
|
"videoConfirmTime": ,
|
||||||
|
"finishTime": ,
|
||||||
|
"advise": "请注意讲解速度",
|
||||||
|
"createdAt": 1734498546322,
|
||||||
|
"updatedAt": 1734498546322
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 更新课程任务
|
||||||
|
|
||||||
|
- **接口**:`PUT /api/lesson-tasks/{id}`
|
||||||
|
- **描述**:更新指定 ID 的课程任务
|
||||||
|
- **认证**:需要
|
||||||
|
- **路径参数**:
|
||||||
|
- `id`: 课程任务 ID
|
||||||
|
- **请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"courseName": "Updated Course", // 课程名称
|
||||||
|
"microLessonName": "Updated Lesson", // 微课名称
|
||||||
|
"userId": 12, // 用户ID
|
||||||
|
"progressStatus": 2, // 进度状态
|
||||||
|
"scriptUploadTime": 1734498510000, // 脚本上传时间
|
||||||
|
"scriptConfirmTime": 1734498510000, // 脚本确认时间
|
||||||
|
"videoCaptureTime": 1734498510000, // 视频录制时间
|
||||||
|
"videoConfirmTime": 1734498510000, // 视频确认时间
|
||||||
|
"finishTime": 1734498510000, // 完成时间
|
||||||
|
"advise": "Updated advice" // 建议
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **成功响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 10000,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"id": 6,
|
||||||
|
"courseName": "Updated Course",
|
||||||
|
"microLessonName": "Updated Lesson",
|
||||||
|
"userId": 12,
|
||||||
|
"progressStatus": 2,
|
||||||
|
"scriptUploadTime": 1734498510000,
|
||||||
|
"scriptConfirmTime": 1734498510000,
|
||||||
|
"videoCaptureTime": 1734498510000,
|
||||||
|
"videoConfirmTime": 1734498510000,
|
||||||
|
"finishTime": 1734498510000,
|
||||||
|
"advise": "Updated advice",
|
||||||
|
"createdAt": 1734498546322,
|
||||||
|
"updatedAt": 1734498586574
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 删除课程任务
|
||||||
|
|
||||||
|
- **接口**:`DELETE /api/lesson-tasks/{id}`
|
||||||
|
- **描述**:删除指定 ID 的课程任务
|
||||||
|
- **认证**:需要
|
||||||
|
- **路径参数**:
|
||||||
|
- `id`: 课程任务 ID
|
||||||
|
- **成功响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 10000,
|
||||||
|
"message": "成功",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 所有需要认证的接口必须在请求头中携带有效的 JWT 令牌
|
||||||
|
2. 所有时间戳字段均为毫秒级时间戳
|
||||||
|
3. 分页接口的页码从 1 开始
|
||||||
|
4. 用户密码在传输和存储时都会进行加密处理
|
||||||
|
5. 课程任务的 progressStatus 字段状态码说明:
|
||||||
|
- 0: 脚本上传
|
||||||
|
- 1: 脚本确认
|
||||||
|
- 2: 视频拍摄
|
||||||
|
- 3: 后期制作
|
||||||
|
- 4: 任务完成
|
||||||
|
6. 用户状态说明:
|
||||||
|
- 1: 正常
|
||||||
|
- 0: 禁用
|
259
mvnw
vendored
Executable file
259
mvnw
vendored
Executable file
@ -0,0 +1,259 @@
|
|||||||
|
#!/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.2
|
||||||
|
#
|
||||||
|
# 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:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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 <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.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${0##*/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
|
||||||
|
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
|
||||||
|
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
|
||||||
|
|
||||||
|
clean || :
|
||||||
|
exec_maven "$@"
|
149
mvnw.cmd
vendored
Normal file
149
mvnw.cmd
vendored
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<# : 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.2
|
||||||
|
@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) { "/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_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
|
||||||
|
if ($env:MAVEN_USER_HOME) {
|
||||||
|
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
|
||||||
|
}
|
||||||
|
$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::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
|
||||||
|
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -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"
|
113
pom.xml
Normal file
113
pom.xml
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<?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.2.1</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
<groupId>com.huertian</groupId>
|
||||||
|
<artifactId>jinduguanli</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<name>jinduguanli</name>
|
||||||
|
<description>jinduguanli</description>
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</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>jakarta.servlet</groupId>
|
||||||
|
<artifactId>jakarta.servlet-api</artifactId>
|
||||||
|
<version>6.0.0</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<version>8.2.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.30</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</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>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
<version>0.11.5</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-impl</artifactId>
|
||||||
|
<version>0.11.5</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
|
<version>0.11.5</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-pool2</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
BIN
src/.DS_Store
vendored
Normal file
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
src/main/.DS_Store
vendored
Normal file
BIN
src/main/.DS_Store
vendored
Normal file
Binary file not shown.
@ -0,0 +1,13 @@
|
|||||||
|
package com.huertian.jinduguanli;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class JinduguanliApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(JinduguanliApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
package com.huertian.jinduguanli.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public class ApiResponse<T> {
|
||||||
|
private int code;
|
||||||
|
private String message;
|
||||||
|
private T data;
|
||||||
|
|
||||||
|
public ApiResponse() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse(int code, String message, T data) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> success() {
|
||||||
|
return new ApiResponse<>(10000, "成功", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> success(T data) {
|
||||||
|
return new ApiResponse<>(10000, "成功", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> error(int code) {
|
||||||
|
return new ApiResponse<>(code, getMessageByCode(code), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> error(int code, String message) {
|
||||||
|
return new ApiResponse<>(code, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getMessageByCode(int code) {
|
||||||
|
switch (code) {
|
||||||
|
case 10001:
|
||||||
|
return "参数无效";
|
||||||
|
case 10002:
|
||||||
|
return "邮箱已存在";
|
||||||
|
case 10003:
|
||||||
|
return "用户不存在";
|
||||||
|
case 10004:
|
||||||
|
return "密码错误";
|
||||||
|
case 10005:
|
||||||
|
return "用户已禁用";
|
||||||
|
case 10006:
|
||||||
|
return "未授权";
|
||||||
|
case 10007:
|
||||||
|
return "令牌已过期";
|
||||||
|
case 10008:
|
||||||
|
return "无效的令牌";
|
||||||
|
case 10009:
|
||||||
|
return "系统错误";
|
||||||
|
default:
|
||||||
|
return "未知错误";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ApiResponse{" +
|
||||||
|
"code=" + code +
|
||||||
|
", message='" + message + '\'' +
|
||||||
|
", data=" + data +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
14
src/main/java/com/huertian/jinduguanli/common/ErrorCode.java
Normal file
14
src/main/java/com/huertian/jinduguanli/common/ErrorCode.java
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package com.huertian.jinduguanli.common;
|
||||||
|
|
||||||
|
public class ErrorCode {
|
||||||
|
public static final int SUCCESS = 10000;
|
||||||
|
public static final int SYSTEM_ERROR = 10001;
|
||||||
|
public static final int INVALID_PARAM = 10002;
|
||||||
|
public static final int USER_NOT_FOUND = 10003;
|
||||||
|
public static final int USER_NOT_FOUND_OR_DISABLED = 10004;
|
||||||
|
public static final int EMAIL_EXISTS = 10005;
|
||||||
|
public static final int PASSWORD_INCORRECT = 10006;
|
||||||
|
public static final int INVALID_TOKEN = 10007;
|
||||||
|
public static final int UNAUTHORIZED = 10008;
|
||||||
|
public static final int USERNAME_EXISTS = 10009;
|
||||||
|
}
|
28
src/main/java/com/huertian/jinduguanli/common/Result.java
Normal file
28
src/main/java/com/huertian/jinduguanli/common/Result.java
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package com.huertian.jinduguanli.common;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Result<T> {
|
||||||
|
private Integer code;
|
||||||
|
private String message;
|
||||||
|
private T data;
|
||||||
|
|
||||||
|
public Result(Integer 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<>(0, "success", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Result<T> error(String message) {
|
||||||
|
return new Result<>(10009, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Result<T> error(Integer code, String message) {
|
||||||
|
return new Result<>(code, message, null);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.huertian.jinduguanli.config;
|
||||||
|
|
||||||
|
import com.huertian.jinduguanli.common.ApiResponse;
|
||||||
|
import jakarta.persistence.EntityNotFoundException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||||
|
|
||||||
|
@ExceptionHandler(EntityNotFoundException.class)
|
||||||
|
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||||
|
public ApiResponse<Void> handleEntityNotFoundException(EntityNotFoundException e) {
|
||||||
|
logger.error("Entity not found: {}", e.getMessage());
|
||||||
|
return ApiResponse.error(10003, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(IllegalArgumentException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public ApiResponse<Void> handleIllegalArgumentException(IllegalArgumentException e) {
|
||||||
|
logger.error("Invalid argument: {}", e.getMessage());
|
||||||
|
return ApiResponse.error(10001, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public ApiResponse<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
|
||||||
|
logger.error("Invalid request body: {}", e.getMessage());
|
||||||
|
return ApiResponse.error(10001, "请求体格式不正确");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
public ApiResponse<Void> handleException(Exception e) {
|
||||||
|
logger.error("Unexpected error: ", e);
|
||||||
|
return ApiResponse.error(10009, "系统错误");
|
||||||
|
}
|
||||||
|
}
|
27
src/main/java/com/huertian/jinduguanli/config/JwtConfig.java
Normal file
27
src/main/java/com/huertian/jinduguanli/config/JwtConfig.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package com.huertian.jinduguanli.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "jwt")
|
||||||
|
public class JwtConfig {
|
||||||
|
private long defaultExpirationMs = 86400000; // 默认24小时
|
||||||
|
private long rememberExpirationMs = 604800000; // 默认7天
|
||||||
|
|
||||||
|
public long getDefaultExpirationMs() {
|
||||||
|
return defaultExpirationMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultExpirationMs(long defaultExpirationMs) {
|
||||||
|
this.defaultExpirationMs = defaultExpirationMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRememberExpirationMs() {
|
||||||
|
return rememberExpirationMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRememberExpirationMs(long rememberExpirationMs) {
|
||||||
|
this.rememberExpirationMs = rememberExpirationMs;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package com.huertian.jinduguanli.config;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||||
|
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
|
||||||
|
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||||
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableCaching
|
||||||
|
public class RedisConfig {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RedisConfig.class);
|
||||||
|
|
||||||
|
@Value("${spring.redis.host}")
|
||||||
|
private String redisHost;
|
||||||
|
|
||||||
|
@Value("${spring.redis.port}")
|
||||||
|
private int redisPort;
|
||||||
|
|
||||||
|
@Value("${spring.redis.password}")
|
||||||
|
private String redisPassword;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public LettuceConnectionFactory redisConnectionFactory() {
|
||||||
|
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisHost, redisPort);
|
||||||
|
if (redisPassword != null && !redisPassword.isEmpty()) {
|
||||||
|
config.setPassword(redisPassword);
|
||||||
|
}
|
||||||
|
logger.info("Configuring Redis connection to {}:{}", redisHost, redisPort);
|
||||||
|
return new LettuceConnectionFactory(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
|
||||||
|
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
|
||||||
|
.entryTtl(Duration.ofMinutes(60))
|
||||||
|
.disableCachingNullValues();
|
||||||
|
|
||||||
|
return RedisCacheManager.builder(factory)
|
||||||
|
.cacheDefaults(config)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
|
||||||
|
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||||
|
template.setConnectionFactory(factory);
|
||||||
|
|
||||||
|
// 设置key的序列化方式
|
||||||
|
template.setKeySerializer(new StringRedisSerializer());
|
||||||
|
// 设置value的序列化方式
|
||||||
|
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
|
||||||
|
// 设置hash key的序列化方式
|
||||||
|
template.setHashKeySerializer(new StringRedisSerializer());
|
||||||
|
// 设置hash value的序列化方式
|
||||||
|
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
|
||||||
|
|
||||||
|
template.afterPropertiesSet();
|
||||||
|
|
||||||
|
// 测试连接
|
||||||
|
try {
|
||||||
|
template.getConnectionFactory().getConnection().ping();
|
||||||
|
logger.info("Redis连接测试成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Redis连接测试失败: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package com.huertian.jinduguanli.config;
|
||||||
|
|
||||||
|
import com.huertian.jinduguanli.security.JwtAuthenticationFilter;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
|
||||||
|
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||||
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
|
||||||
|
.requestMatchers("/api/users/login").permitAll()
|
||||||
|
.requestMatchers(HttpMethod.POST, "/api/users").permitAll()
|
||||||
|
.requestMatchers(HttpMethod.GET, "/api/lesson/task/**").authenticated()
|
||||||
|
.anyRequest().authenticated())
|
||||||
|
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CorsConfigurationSource corsConfigurationSource() {
|
||||||
|
CorsConfiguration configuration = new CorsConfiguration();
|
||||||
|
configuration.setAllowedOrigins(Arrays.asList("*"));
|
||||||
|
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||||
|
configuration.setAllowedHeaders(Arrays.asList("*"));
|
||||||
|
configuration.setExposedHeaders(Arrays.asList("*"));
|
||||||
|
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/**", configuration);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||||
|
return config.getAuthenticationManager();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
package com.huertian.jinduguanli.controller;
|
||||||
|
|
||||||
|
import com.huertian.jinduguanli.common.ApiResponse;
|
||||||
|
import com.huertian.jinduguanli.common.ErrorCode;
|
||||||
|
import com.huertian.jinduguanli.dto.LessonTaskRequest;
|
||||||
|
import com.huertian.jinduguanli.entity.LessonTask;
|
||||||
|
import com.huertian.jinduguanli.service.LessonTaskService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/lesson-tasks")
|
||||||
|
public class LessonTaskController {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(LessonTaskController.class);
|
||||||
|
private final LessonTaskService lessonTaskService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public LessonTaskController(LessonTaskService lessonTaskService) {
|
||||||
|
this.lessonTaskService = lessonTaskService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ApiResponse<Page<LessonTask>> listTasks(
|
||||||
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(defaultValue = "10") Integer size,
|
||||||
|
@RequestParam(required = false) Long userId) {
|
||||||
|
logger.info("收到课程任务列表查询请求,页码: {},每页数量: {},用户ID: {}", page, size, userId);
|
||||||
|
try {
|
||||||
|
Page<LessonTask> tasks = lessonTaskService.findAll(PageRequest.of(page - 1, size), userId);
|
||||||
|
return ApiResponse.success(tasks);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("查询课程任务列表失败", e);
|
||||||
|
return new ApiResponse<>(ErrorCode.SYSTEM_ERROR, "获取课程任务列表失败", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResponse<LessonTask> getTask(@PathVariable Long id) {
|
||||||
|
logger.info("收到课程任务查询请求,任务ID: {}", id);
|
||||||
|
try {
|
||||||
|
LessonTask task = lessonTaskService.findById(id);
|
||||||
|
return ApiResponse.success(task);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("查询课程任务失败", e);
|
||||||
|
return new ApiResponse<>(ErrorCode.SYSTEM_ERROR, "获取课程任务失败", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ApiResponse<LessonTask> createTask(@Valid @RequestBody LessonTaskRequest request) {
|
||||||
|
logger.info("收到创建课程任务请求");
|
||||||
|
try {
|
||||||
|
LessonTask task = lessonTaskService.create(request);
|
||||||
|
return ApiResponse.success(task);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("创建课程任务失败", e);
|
||||||
|
return new ApiResponse<>(ErrorCode.SYSTEM_ERROR, "创建课程任务失败", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ApiResponse<LessonTask> updateTask(@PathVariable Long id, @Valid @RequestBody LessonTaskRequest request) {
|
||||||
|
logger.info("收到更新课程任务请求,任务ID: {}", id);
|
||||||
|
try {
|
||||||
|
LessonTask task = lessonTaskService.update(id, request);
|
||||||
|
return ApiResponse.success(task);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("更新课程任务失败", e);
|
||||||
|
return new ApiResponse<>(ErrorCode.SYSTEM_ERROR, "更新课程任务失败", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResponse<Void> deleteTask(@PathVariable Long id) {
|
||||||
|
logger.info("收到删除课程任务请求,任务ID: {}", id);
|
||||||
|
try {
|
||||||
|
lessonTaskService.delete(id);
|
||||||
|
return ApiResponse.success();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("删除课程任务失败", e);
|
||||||
|
return new ApiResponse<>(ErrorCode.SYSTEM_ERROR, "删除课程任务失败", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
package com.huertian.jinduguanli.controller;
|
||||||
|
|
||||||
|
import com.huertian.jinduguanli.common.ApiResponse;
|
||||||
|
import com.huertian.jinduguanli.dto.CreateUserRequest;
|
||||||
|
import com.huertian.jinduguanli.dto.LoginResponse;
|
||||||
|
import com.huertian.jinduguanli.dto.UserLoginRequest;
|
||||||
|
import com.huertian.jinduguanli.entity.User;
|
||||||
|
import com.huertian.jinduguanli.security.service.JwtService;
|
||||||
|
import com.huertian.jinduguanli.security.service.TokenBlacklistService;
|
||||||
|
import com.huertian.jinduguanli.service.UserService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/users")
|
||||||
|
public class UserController {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
private final JwtService jwtService;
|
||||||
|
private final TokenBlacklistService tokenBlacklistService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public UserController(
|
||||||
|
UserService userService,
|
||||||
|
JwtService jwtService,
|
||||||
|
TokenBlacklistService tokenBlacklistService) {
|
||||||
|
this.userService = userService;
|
||||||
|
this.jwtService = jwtService;
|
||||||
|
this.tokenBlacklistService = tokenBlacklistService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public ApiResponse<LoginResponse> login(@Valid @RequestBody UserLoginRequest request) {
|
||||||
|
logger.info("收到登录请求,邮箱: {}", request.getEmail());
|
||||||
|
ApiResponse<LoginResponse> response = userService.login(request);
|
||||||
|
logger.info("登录响应: {}", response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/logout")
|
||||||
|
public ApiResponse<String> logout(@RequestHeader("Authorization") String authHeader) {
|
||||||
|
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
||||||
|
String token = authHeader.substring(7);
|
||||||
|
// 获取token的过期时间
|
||||||
|
long expiration = jwtService.extractExpiration(token).getTime();
|
||||||
|
// 将token加入黑名单
|
||||||
|
tokenBlacklistService.addToBlacklist(token, expiration);
|
||||||
|
return ApiResponse.success("登出成功");
|
||||||
|
}
|
||||||
|
return ApiResponse.success("登出成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ApiResponse<Void> createUser(@Valid @RequestBody CreateUserRequest request) {
|
||||||
|
logger.info("收到创建用户请求,用户名: {}", request.getUsername());
|
||||||
|
return userService.createUser(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/current")
|
||||||
|
public ApiResponse<User> getCurrentUser(@RequestHeader("Authorization") String authHeader) {
|
||||||
|
logger.info("收到获取当前用户信息请求,Authorization: {}", authHeader);
|
||||||
|
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
||||||
|
String token = authHeader.substring(7);
|
||||||
|
logger.info("解析出的token: {}", token);
|
||||||
|
return userService.getCurrentUser(token);
|
||||||
|
}
|
||||||
|
logger.warn("未提供有效的认证令牌");
|
||||||
|
return ApiResponse.error(401, "未提供有效的认证令牌");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
public ApiResponse<?> listUsers(
|
||||||
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(defaultValue = "10") Integer limit) {
|
||||||
|
logger.info("收到用户列表分页查询请求,页码: {},每页数量: {}", page, limit);
|
||||||
|
return userService.listUsers(page, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/disable/{userId}")
|
||||||
|
public ApiResponse<String> disableUser(@PathVariable Long userId) {
|
||||||
|
logger.info("收到禁用用户请求,用户ID: {}", userId);
|
||||||
|
return userService.disableUser(userId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package com.huertian.jinduguanli.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class CreateUserRequest {
|
||||||
|
@NotBlank(message = "用户名不能为空")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@NotBlank(message = "邮箱不能为空")
|
||||||
|
@Email(message = "邮箱格式不正确")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@NotBlank(message = "密码不能为空")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@NotNull(message = "部门ID不能为空")
|
||||||
|
private Long departmentId;
|
||||||
|
|
||||||
|
@NotNull(message = "角色不能为空")
|
||||||
|
private Integer roles;
|
||||||
|
|
||||||
|
@NotNull(message = "岗位不能为空")
|
||||||
|
private Integer jobs;
|
||||||
|
|
||||||
|
@NotNull(message = "创建者ID不能为空")
|
||||||
|
private Long creatorId;
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getDepartmentId() {
|
||||||
|
return departmentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDepartmentId(Long departmentId) {
|
||||||
|
this.departmentId = departmentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getRoles() {
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoles(Integer roles) {
|
||||||
|
this.roles = roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getJobs() {
|
||||||
|
return jobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJobs(Integer jobs) {
|
||||||
|
this.jobs = jobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCreatorId() {
|
||||||
|
return creatorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatorId(Long creatorId) {
|
||||||
|
this.creatorId = creatorId;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
package com.huertian.jinduguanli.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ToString
|
||||||
|
public class LessonTaskRequest {
|
||||||
|
private String courseName;
|
||||||
|
private String microLessonName;
|
||||||
|
private Long userId;
|
||||||
|
private Integer progressStatus;
|
||||||
|
private Long scriptUploadTime;
|
||||||
|
private Long scriptConfirmTime;
|
||||||
|
private Long videoCaptureTime;
|
||||||
|
private Long videoConfirmTime;
|
||||||
|
private Long finishTime;
|
||||||
|
private String advise;
|
||||||
|
|
||||||
|
public String getCourseName() {
|
||||||
|
return courseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCourseName(String courseName) {
|
||||||
|
this.courseName = courseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMicroLessonName() {
|
||||||
|
return microLessonName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMicroLessonName(String microLessonName) {
|
||||||
|
this.microLessonName = microLessonName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(Long userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getProgressStatus() {
|
||||||
|
return progressStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProgressStatus(Integer progressStatus) {
|
||||||
|
this.progressStatus = progressStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getScriptUploadTime() {
|
||||||
|
return scriptUploadTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScriptUploadTime(Long scriptUploadTime) {
|
||||||
|
this.scriptUploadTime = scriptUploadTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getScriptConfirmTime() {
|
||||||
|
return scriptConfirmTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScriptConfirmTime(Long scriptConfirmTime) {
|
||||||
|
this.scriptConfirmTime = scriptConfirmTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getVideoCaptureTime() {
|
||||||
|
return videoCaptureTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVideoCaptureTime(Long videoCaptureTime) {
|
||||||
|
this.videoCaptureTime = videoCaptureTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getVideoConfirmTime() {
|
||||||
|
return videoConfirmTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVideoConfirmTime(Long videoConfirmTime) {
|
||||||
|
this.videoConfirmTime = videoConfirmTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getFinishTime() {
|
||||||
|
return finishTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFinishTime(Long finishTime) {
|
||||||
|
this.finishTime = finishTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAdvise() {
|
||||||
|
return advise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAdvise(String advise) {
|
||||||
|
this.advise = advise;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.huertian.jinduguanli.dto;
|
||||||
|
|
||||||
|
public class LoginResponse {
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
public LoginResponse(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package com.huertian.jinduguanli.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public class UserLoginRequest {
|
||||||
|
@NotBlank(message = "邮箱不能为空")
|
||||||
|
@Email(message = "邮箱格式不正确")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@NotBlank(message = "密码不能为空")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
private Boolean remember = false;
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getRemember() {
|
||||||
|
return remember;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemember(Boolean remember) {
|
||||||
|
this.remember = remember != null ? remember : false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package com.huertian.jinduguanli.dto;
|
||||||
|
|
||||||
|
import com.huertian.jinduguanli.entity.User;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class UserPageResponse {
|
||||||
|
private List<User> list;
|
||||||
|
private Long total;
|
||||||
|
private Integer currentPage;
|
||||||
|
private Integer pageSize;
|
||||||
|
|
||||||
|
public List<User> getList() {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setList(List<User> list) {
|
||||||
|
this.list = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTotal() {
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotal(Long total) {
|
||||||
|
this.total = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCurrentPage() {
|
||||||
|
return currentPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentPage(Integer currentPage) {
|
||||||
|
this.currentPage = currentPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPageSize() {
|
||||||
|
return pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPageSize(Integer pageSize) {
|
||||||
|
this.pageSize = pageSize;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package com.huertian.jinduguanli.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "lesson_tasks")
|
||||||
|
@Data
|
||||||
|
@ToString
|
||||||
|
public class LessonTask implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(LessonTask.class);
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String courseName;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String microLessonName;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Integer progressStatus;
|
||||||
|
|
||||||
|
private Long scriptUploadTime;
|
||||||
|
private Long scriptConfirmTime;
|
||||||
|
private Long videoCaptureTime;
|
||||||
|
private Long videoConfirmTime;
|
||||||
|
private Long finishTime;
|
||||||
|
|
||||||
|
private String advise;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Long createdAt;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Long updatedAt;
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
protected void onCreate() {
|
||||||
|
logger.info("创建新课程任务 - 课程名称: {}, 小课名称: {}, 用户ID: {}",
|
||||||
|
this.courseName, this.microLessonName, this.userId);
|
||||||
|
progressStatus = 1;
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
createdAt = now;
|
||||||
|
updatedAt = now;
|
||||||
|
logger.info("创建课程任务成功 - 进度状态: {}, 创建时间: {}, 更新时间: {}",
|
||||||
|
progressStatus, createdAt, updatedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreUpdate
|
||||||
|
protected void onUpdate() {
|
||||||
|
logger.info("更新课程任务 - ID: {}, 课程名称: {}, 小课名称: {}",
|
||||||
|
this.id, this.courseName, this.microLessonName);
|
||||||
|
updatedAt = System.currentTimeMillis();
|
||||||
|
logger.info("更新课程任务成功 - ID: {}, 更新时间: {}", id, updatedAt);
|
||||||
|
}
|
||||||
|
}
|
173
src/main/java/com/huertian/jinduguanli/entity/User.java
Normal file
173
src/main/java/com/huertian/jinduguanli/entity/User.java
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
package com.huertian.jinduguanli.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "users")
|
||||||
|
public class User implements UserDetails {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Column(nullable = false, unique = true)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@Column(name = "department_id")
|
||||||
|
private Long departmentId;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private Integer roles = 0;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private Integer jobs = 0;
|
||||||
|
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Column(name = "creator_id")
|
||||||
|
private Long creatorId = 0L;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Column(name = "created_at", nullable = false)
|
||||||
|
private Long createdAt;
|
||||||
|
|
||||||
|
@Column(name = "updated_at", nullable = false)
|
||||||
|
private Long updatedAt;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return username; // 返回 username 而不是 email
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonExpired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonLocked() {
|
||||||
|
return status == 1; // 假设 status == 1 表示账户未锁定
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCredentialsNonExpired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return status == 1; // 假设 status == 1 表示账户启用
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters and setters
|
||||||
|
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 void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getDepartmentId() {
|
||||||
|
return departmentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDepartmentId(Long departmentId) {
|
||||||
|
this.departmentId = departmentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getRoles() {
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoles(Integer roles) {
|
||||||
|
this.roles = roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getJobs() {
|
||||||
|
return jobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJobs(Integer jobs) {
|
||||||
|
this.jobs = jobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAvatar() {
|
||||||
|
return avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAvatar(String avatar) {
|
||||||
|
this.avatar = avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCreatorId() {
|
||||||
|
return creatorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatorId(Long creatorId) {
|
||||||
|
this.creatorId = creatorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(Integer status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(Long createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getUpdatedAt() {
|
||||||
|
return updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdatedAt(Long updatedAt) {
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.huertian.jinduguanli.repository;
|
||||||
|
|
||||||
|
import com.huertian.jinduguanli.entity.LessonTask;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface LessonTaskRepository extends JpaRepository<LessonTask, Long> {
|
||||||
|
Page<LessonTask> findByUserId(Long userId, Pageable pageable);
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.huertian.jinduguanli.repository;
|
||||||
|
|
||||||
|
import com.huertian.jinduguanli.entity.User;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
|
Optional<User> findByEmail(String email);
|
||||||
|
|
||||||
|
Optional<User> findByUsername(String username);
|
||||||
|
|
||||||
|
boolean existsByEmail(String email);
|
||||||
|
|
||||||
|
@Query(value = "SELECT * FROM users LIMIT :limit OFFSET :offset", nativeQuery = true)
|
||||||
|
List<User> findAllWithPagination(@Param("offset") int offset, @Param("limit") int limit);
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package com.huertian.jinduguanli.security;
|
||||||
|
|
||||||
|
import com.huertian.jinduguanli.entity.User;
|
||||||
|
import com.huertian.jinduguanli.repository.UserRepository;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CustomUserDetailsService implements UserDetailsService {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(CustomUserDetailsService.class);
|
||||||
|
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public CustomUserDetailsService(UserRepository userRepository) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
|
||||||
|
logger.info("Loading user details for email: {}", email);
|
||||||
|
|
||||||
|
User user = userRepository.findByEmail(email)
|
||||||
|
.orElseThrow(() -> {
|
||||||
|
logger.error("User not found with email: {}", email);
|
||||||
|
return new UsernameNotFoundException("User not found with email: " + email);
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info("User found with email: {}", email);
|
||||||
|
|
||||||
|
return new org.springframework.security.core.userdetails.User(
|
||||||
|
user.getEmail(),
|
||||||
|
user.getPassword(),
|
||||||
|
new ArrayList<>());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
package com.huertian.jinduguanli.security;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.huertian.jinduguanli.common.ApiResponse;
|
||||||
|
import com.huertian.jinduguanli.common.ErrorCode;
|
||||||
|
import com.huertian.jinduguanli.security.service.JwtService;
|
||||||
|
import com.huertian.jinduguanli.security.service.TokenBlacklistService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
|
||||||
|
private final JwtService jwtService;
|
||||||
|
private final UserDetailsService userDetailsService;
|
||||||
|
private final TokenBlacklistService tokenBlacklistService;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
public JwtAuthenticationFilter(
|
||||||
|
JwtService jwtService,
|
||||||
|
UserDetailsService userDetailsService,
|
||||||
|
TokenBlacklistService tokenBlacklistService,
|
||||||
|
ObjectMapper objectMapper) {
|
||||||
|
this.jwtService = jwtService;
|
||||||
|
this.userDetailsService = userDetailsService;
|
||||||
|
this.tokenBlacklistService = tokenBlacklistService;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(
|
||||||
|
@NonNull HttpServletRequest request,
|
||||||
|
@NonNull HttpServletResponse response,
|
||||||
|
@NonNull FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 允许OPTIONS请求通过
|
||||||
|
if (request.getMethod().equals(HttpMethod.OPTIONS.name())) {
|
||||||
|
logger.debug("OPTIONS请求,直接放行");
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String authHeader = request.getHeader("Authorization");
|
||||||
|
logger.debug("处理请求认证 - 请求路径: {}", request.getRequestURI());
|
||||||
|
|
||||||
|
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||||
|
logger.debug("未提供认证令牌或格式不正确");
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String jwt = authHeader.substring(7);
|
||||||
|
final String username = jwtService.extractUsername(jwt);
|
||||||
|
|
||||||
|
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||||
|
logger.debug("开始验证用户令牌 - 用户邮箱: {}", username);
|
||||||
|
|
||||||
|
if (tokenBlacklistService.isBlacklisted(jwt)) {
|
||||||
|
logger.warn("令牌已被加入黑名单");
|
||||||
|
handleJwtException(response, "令牌已被加入黑名单");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
|
||||||
|
|
||||||
|
if (jwtService.isTokenValid(jwt, userDetails)) {
|
||||||
|
logger.debug("令牌验证成功,设置安全上下文");
|
||||||
|
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
|
||||||
|
userDetails,
|
||||||
|
null,
|
||||||
|
userDetails.getAuthorities());
|
||||||
|
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authToken);
|
||||||
|
} else {
|
||||||
|
logger.warn("令牌验证失败");
|
||||||
|
handleJwtException(response, "令牌验证失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("处理认证时发生错误", e);
|
||||||
|
handleJwtException(response, "认证处理失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleJwtException(HttpServletResponse response, String message) throws IOException {
|
||||||
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
|
ApiResponse<?> apiResponse = ApiResponse.error(ErrorCode.INVALID_TOKEN, "token已过期,请重新登录");
|
||||||
|
response.getWriter().write(objectMapper.writeValueAsString(apiResponse));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
package com.huertian.jinduguanli.security.service;
|
||||||
|
|
||||||
|
import com.huertian.jinduguanli.entity.User;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
import io.jsonwebtoken.io.Decoders;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.security.Key;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class JwtService {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(JwtService.class);
|
||||||
|
|
||||||
|
@Value("${jwt.secret}")
|
||||||
|
private String secretKey;
|
||||||
|
|
||||||
|
@Value("${jwt.expiration}")
|
||||||
|
private long jwtExpiration;
|
||||||
|
|
||||||
|
public String extractUsername(String token) {
|
||||||
|
return extractClaim(token, Claims::getSubject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date extractExpiration(String token) {
|
||||||
|
return extractClaim(token, Claims::getExpiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
|
||||||
|
final Claims claims = extractAllClaims(token);
|
||||||
|
return claimsResolver.apply(claims);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateToken(UserDetails userDetails) {
|
||||||
|
return generateToken(new HashMap<>(), userDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
|
||||||
|
Date issuedAt = new Date(System.currentTimeMillis());
|
||||||
|
Date expiration = new Date(System.currentTimeMillis() + jwtExpiration);
|
||||||
|
logger.debug("Generating token for user: {}, expiration: {}, jwtExpiration: {}", userDetails.getUsername(),
|
||||||
|
expiration, jwtExpiration);
|
||||||
|
|
||||||
|
return Jwts.builder()
|
||||||
|
.setClaims(extraClaims)
|
||||||
|
.setSubject(((User) userDetails).getEmail())
|
||||||
|
.setIssuedAt(issuedAt)
|
||||||
|
.setExpiration(expiration)
|
||||||
|
.signWith(getSignInKey(), SignatureAlgorithm.HS384)
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTokenValid(String token, UserDetails userDetails) {
|
||||||
|
try {
|
||||||
|
final String username = extractUsername(token);
|
||||||
|
Date expiration = extractExpiration(token);
|
||||||
|
Date now = new Date();
|
||||||
|
boolean isExpired = expiration.before(now);
|
||||||
|
boolean isValid = username.equals(((User) userDetails).getEmail()) && !isExpired;
|
||||||
|
logger.debug("Token validation for user {}: valid={}, expiration={}, current={}, isExpired={}",
|
||||||
|
username, isValid, expiration, now, isExpired);
|
||||||
|
return isValid;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Token validation failed: {}", e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// private boolean isTokenExpired(String token) {
|
||||||
|
// Date expiration = extractExpiration(token);
|
||||||
|
// Date now = new Date();
|
||||||
|
// boolean isExpired = expiration.before(now);
|
||||||
|
// logger.debug("Token expiration check: expiration={}, current={},
|
||||||
|
// isExpired={}",
|
||||||
|
// expiration, now, isExpired);
|
||||||
|
// return isExpired;
|
||||||
|
// }
|
||||||
|
|
||||||
|
private Claims extractAllClaims(String token) {
|
||||||
|
try {
|
||||||
|
return Jwts.parserBuilder()
|
||||||
|
.setSigningKey(getSignInKey())
|
||||||
|
.build()
|
||||||
|
.parseClaimsJws(token)
|
||||||
|
.getBody();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("解析token失败: {}", e.getMessage());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Key getSignInKey() {
|
||||||
|
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
|
||||||
|
return Keys.hmacShaKeyFor(keyBytes);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package com.huertian.jinduguanli.security.service;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TokenBlacklistService {
|
||||||
|
private final StringRedisTemplate redisTemplate;
|
||||||
|
private static final String BLACKLIST_PREFIX = "token:blacklist:";
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(TokenBlacklistService.class);
|
||||||
|
|
||||||
|
public TokenBlacklistService(StringRedisTemplate redisTemplate) {
|
||||||
|
this.redisTemplate = redisTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将令牌添加到黑名单
|
||||||
|
*
|
||||||
|
* @param token JWT令牌
|
||||||
|
* @param expiration 过期时间(毫秒)
|
||||||
|
*/
|
||||||
|
public void addToBlacklist(String token, long expiration) {
|
||||||
|
logger.info("将令牌加入黑名单 - 过期时间: {}", new Date(expiration));
|
||||||
|
try {
|
||||||
|
String key = BLACKLIST_PREFIX + token;
|
||||||
|
long ttl = expiration - System.currentTimeMillis();
|
||||||
|
if (ttl > 0) {
|
||||||
|
redisTemplate.opsForValue().set(key, "blacklisted", ttl, TimeUnit.MILLISECONDS);
|
||||||
|
logger.info("令牌已成功加入黑名单");
|
||||||
|
} else {
|
||||||
|
logger.warn("令牌已过期,无需加入黑名单");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("将令牌加入黑名单时发生错误", e);
|
||||||
|
throw new RuntimeException("无法将令牌加入黑名单", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查令牌是否在黑名单中
|
||||||
|
*
|
||||||
|
* @param token JWT令牌
|
||||||
|
* @return 如果在黑名单中返回true
|
||||||
|
*/
|
||||||
|
public boolean isBlacklisted(String token) {
|
||||||
|
logger.debug("检查令牌是否在黑名单中");
|
||||||
|
try {
|
||||||
|
String key = BLACKLIST_PREFIX + token;
|
||||||
|
Boolean exists = redisTemplate.hasKey(key);
|
||||||
|
logger.debug("令牌黑名单检查结果: {}", exists);
|
||||||
|
return Boolean.TRUE.equals(exists);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("检查令牌黑名单状态时发生错误", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.huertian.jinduguanli.security.service;
|
||||||
|
|
||||||
|
import com.huertian.jinduguanli.entity.User;
|
||||||
|
import com.huertian.jinduguanli.repository.UserRepository;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(UserDetailsService.class);
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
public UserDetailsService(UserRepository userRepository) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
|
||||||
|
logger.info("开始加载用户信息 - 邮箱: {}", email);
|
||||||
|
|
||||||
|
User user = userRepository.findByEmail(email)
|
||||||
|
.orElseThrow(() -> {
|
||||||
|
logger.error("未找到用户 - 邮箱: {}", email);
|
||||||
|
return new UsernameNotFoundException("未找到用户");
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info("成功加载用户信息 - 用户名: {}", user.getUsername());
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
package com.huertian.jinduguanli.service;
|
||||||
|
|
||||||
|
import com.huertian.jinduguanli.dto.LessonTaskRequest;
|
||||||
|
import com.huertian.jinduguanli.entity.LessonTask;
|
||||||
|
import com.huertian.jinduguanli.repository.LessonTaskRepository;
|
||||||
|
import jakarta.persistence.EntityNotFoundException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.cache.annotation.CacheEvict;
|
||||||
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class LessonTaskService {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(LessonTaskService.class);
|
||||||
|
private final LessonTaskRepository lessonTaskRepository;
|
||||||
|
|
||||||
|
public LessonTaskService(LessonTaskRepository lessonTaskRepository) {
|
||||||
|
this.lessonTaskRepository = lessonTaskRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Cacheable(value = "lessonTasks", key = "#userId != null ? 'user:' + #userId + ':page:' + #pageable.pageNumber : 'all:page:' + #pageable.pageNumber")
|
||||||
|
public Page<LessonTask> findAll(Pageable pageable, Long userId) {
|
||||||
|
logger.info("查询课程任务 - 页码: {}, 每页数量: {}, 用户ID: {}",
|
||||||
|
pageable.getPageNumber(), pageable.getPageSize(), userId);
|
||||||
|
|
||||||
|
Page<LessonTask> result;
|
||||||
|
if (userId != null) {
|
||||||
|
result = lessonTaskRepository.findByUserId(userId, pageable);
|
||||||
|
} else {
|
||||||
|
result = lessonTaskRepository.findAll(pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("查询到 {} 条课程任务", result.getTotalElements());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Cacheable(value = "lessonTask", key = "#id")
|
||||||
|
public LessonTask findById(Long id) {
|
||||||
|
logger.info("根据ID查询课程任务 - 任务ID: {}", id);
|
||||||
|
return lessonTaskRepository.findById(id)
|
||||||
|
.orElseThrow(() -> {
|
||||||
|
logger.error("未找到ID为 {} 的课程任务", id);
|
||||||
|
return new EntityNotFoundException("未找到ID为 " + id + " 的任务");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@CacheEvict(value = { "lessonTasks", "lessonTask" }, allEntries = true)
|
||||||
|
public LessonTask create(LessonTaskRequest request) {
|
||||||
|
logger.info("开始创建课程任务 - 课程名称: {}, 小课名称: {}, 用户ID: {}",
|
||||||
|
request.getCourseName(), request.getMicroLessonName(), request.getUserId());
|
||||||
|
validateRequest(request);
|
||||||
|
LessonTask task = new LessonTask();
|
||||||
|
BeanUtils.copyProperties(request, task);
|
||||||
|
task.setProgressStatus(1); // 初始状态
|
||||||
|
LessonTask savedTask = lessonTaskRepository.save(task);
|
||||||
|
logger.info("创建课程任务成功 - 任务ID: {}", savedTask.getId());
|
||||||
|
return savedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@CacheEvict(value = { "lessonTasks", "lessonTask" }, allEntries = true)
|
||||||
|
public LessonTask update(Long id, LessonTaskRequest request) {
|
||||||
|
logger.info("开始更新课程任务 - 任务ID: {}, 课程名称: {}, 小课名称: {}",
|
||||||
|
id, request.getCourseName(), request.getMicroLessonName());
|
||||||
|
LessonTask task = findById(id);
|
||||||
|
|
||||||
|
// 保留原有的进度状态,如果请求中包含新的进度状态则更新
|
||||||
|
Integer originalStatus = task.getProgressStatus();
|
||||||
|
BeanUtils.copyProperties(request, task);
|
||||||
|
if (task.getProgressStatus() == null) {
|
||||||
|
task.setProgressStatus(originalStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
LessonTask updatedTask = lessonTaskRepository.save(task);
|
||||||
|
logger.info("更新课程任务成功 - 任务ID: {}", updatedTask.getId());
|
||||||
|
return updatedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@CacheEvict(value = { "lessonTasks", "lessonTask" }, allEntries = true)
|
||||||
|
public void delete(Long id) {
|
||||||
|
logger.info("开始删除课程任务 - 任务ID: {}", id);
|
||||||
|
if (!lessonTaskRepository.existsById(id)) {
|
||||||
|
logger.error("未找到ID为 {} 的课程任务", id);
|
||||||
|
throw new EntityNotFoundException("未找到ID为 " + id + " 的任务");
|
||||||
|
}
|
||||||
|
lessonTaskRepository.deleteById(id);
|
||||||
|
logger.info("删除课程任务成功 - 任务ID: {}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateRequest(LessonTaskRequest request) {
|
||||||
|
if (request.getCourseName() == null || request.getCourseName().trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("课程名称不能为空");
|
||||||
|
}
|
||||||
|
if (request.getMicroLessonName() == null || request.getMicroLessonName().trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("小课名称不能为空");
|
||||||
|
}
|
||||||
|
if (request.getUserId() == null) {
|
||||||
|
throw new IllegalArgumentException("用户ID不能为空");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
171
src/main/java/com/huertian/jinduguanli/service/UserService.java
Normal file
171
src/main/java/com/huertian/jinduguanli/service/UserService.java
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
package com.huertian.jinduguanli.service;
|
||||||
|
|
||||||
|
import com.huertian.jinduguanli.common.ApiResponse;
|
||||||
|
import com.huertian.jinduguanli.common.ErrorCode;
|
||||||
|
import com.huertian.jinduguanli.dto.CreateUserRequest;
|
||||||
|
import com.huertian.jinduguanli.dto.LoginResponse;
|
||||||
|
import com.huertian.jinduguanli.dto.UserLoginRequest;
|
||||||
|
import com.huertian.jinduguanli.dto.UserPageResponse;
|
||||||
|
import com.huertian.jinduguanli.entity.User;
|
||||||
|
import com.huertian.jinduguanli.repository.UserRepository;
|
||||||
|
import com.huertian.jinduguanli.security.service.JwtService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserService implements UserDetailsService {
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final JwtService jwtService;
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public UserService(JwtService jwtService, PasswordEncoder passwordEncoder, UserRepository userRepository) {
|
||||||
|
this.jwtService = jwtService;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
|
||||||
|
Optional<User> user = userRepository.findByEmail(email);
|
||||||
|
if (user.isEmpty()) {
|
||||||
|
throw new UsernameNotFoundException("User not found with email: " + email);
|
||||||
|
}
|
||||||
|
return user.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse<LoginResponse> login(UserLoginRequest request) {
|
||||||
|
Optional<User> userOpt = userRepository.findByEmail(request.getEmail());
|
||||||
|
if (userOpt.isEmpty() || userOpt.get().getStatus() != 1) {
|
||||||
|
return new ApiResponse<>(ErrorCode.USER_NOT_FOUND_OR_DISABLED, "用户不存在或已禁用", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = userOpt.get();
|
||||||
|
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
|
||||||
|
return new ApiResponse<>(ErrorCode.PASSWORD_INCORRECT, "密码错误", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
String token = jwtService.generateToken(user);
|
||||||
|
return ApiResponse.success(new LoginResponse(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse<Void> createUser(CreateUserRequest request) {
|
||||||
|
// 检查邮箱是否已存在
|
||||||
|
if (userRepository.findByEmail(request.getEmail()).isPresent()) {
|
||||||
|
return new ApiResponse<>(ErrorCode.EMAIL_EXISTS, "邮箱已存在", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = new User();
|
||||||
|
user.setEmail(request.getEmail());
|
||||||
|
user.setUsername(request.getUsername());
|
||||||
|
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
||||||
|
user.setDepartmentId(request.getDepartmentId());
|
||||||
|
user.setRoles(request.getRoles());
|
||||||
|
user.setJobs(request.getJobs());
|
||||||
|
user.setCreatorId(request.getCreatorId());
|
||||||
|
user.setStatus(1);
|
||||||
|
user.setCreatedAt(System.currentTimeMillis());
|
||||||
|
user.setUpdatedAt(System.currentTimeMillis());
|
||||||
|
|
||||||
|
userRepository.save(user);
|
||||||
|
|
||||||
|
return ApiResponse.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse<User> getCurrentUser(String token) {
|
||||||
|
String email = jwtService.extractUsername(token);
|
||||||
|
logger.info("从token中提取的邮箱: {}", email);
|
||||||
|
Optional<User> userOpt = userRepository.findByEmail(email);
|
||||||
|
if (userOpt.isEmpty()) {
|
||||||
|
logger.warn("用户不存在: {}", email);
|
||||||
|
return new ApiResponse<>(ErrorCode.USER_NOT_FOUND, "用户不存在", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = userOpt.get();
|
||||||
|
logger.info("找到用户: {}", user.getUsername());
|
||||||
|
user.setPassword(null);
|
||||||
|
return ApiResponse.success(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse<String> disableUser(Long userId) {
|
||||||
|
Optional<User> userOpt = userRepository.findById(userId);
|
||||||
|
|
||||||
|
if (userOpt.isEmpty()) {
|
||||||
|
return new ApiResponse<>(ErrorCode.USER_NOT_FOUND, "用户不存在", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = userOpt.get();
|
||||||
|
user.setStatus(0);
|
||||||
|
userRepository.save(user);
|
||||||
|
|
||||||
|
return ApiResponse.success("用户已禁用");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse<UserPageResponse> listUsers(Integer currentPage, Integer pageSize) {
|
||||||
|
// 参数校验
|
||||||
|
if (currentPage < 1) {
|
||||||
|
currentPage = 1;
|
||||||
|
}
|
||||||
|
if (pageSize < 1) {
|
||||||
|
pageSize = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询数据
|
||||||
|
Page<User> userPage = userRepository.findAll(PageRequest.of(currentPage - 1, pageSize));
|
||||||
|
|
||||||
|
// 处理返回数据
|
||||||
|
UserPageResponse response = new UserPageResponse();
|
||||||
|
response.setTotal(userPage.getTotalElements());
|
||||||
|
response.setCurrentPage(currentPage);
|
||||||
|
response.setPageSize(pageSize);
|
||||||
|
|
||||||
|
List<User> users = userPage.getContent();
|
||||||
|
users.forEach(user -> user.setPassword(null));
|
||||||
|
response.setList(users);
|
||||||
|
|
||||||
|
return ApiResponse.success(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public User register(User user) {
|
||||||
|
// 检查邮箱是否已存在
|
||||||
|
if (userRepository.findByEmail(user.getEmail()).isPresent()) {
|
||||||
|
throw new RuntimeException("邮箱已存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户名是否已存在
|
||||||
|
if (userRepository.findByUsername(user.getUsername()).isPresent()) {
|
||||||
|
throw new RuntimeException("用户名已存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置默认值
|
||||||
|
user.setDepartmentId(0L);
|
||||||
|
user.setRoles(0);
|
||||||
|
user.setJobs(0);
|
||||||
|
user.setCreatorId(0L);
|
||||||
|
|
||||||
|
// 加密密码
|
||||||
|
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||||
|
user.setStatus(1);
|
||||||
|
user.setCreatedAt(System.currentTimeMillis());
|
||||||
|
user.setUpdatedAt(System.currentTimeMillis());
|
||||||
|
|
||||||
|
// 保存用户
|
||||||
|
User savedUser = userRepository.save(user);
|
||||||
|
|
||||||
|
// 清除密码后返回
|
||||||
|
savedUser.setPassword(null);
|
||||||
|
return savedUser;
|
||||||
|
}
|
||||||
|
}
|
121
src/main/java/com/huertian/jinduguanli/utils/JwtUtil.java
Normal file
121
src/main/java/com/huertian/jinduguanli/utils/JwtUtil.java
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package com.huertian.jinduguanli.utils;
|
||||||
|
|
||||||
|
import com.huertian.jinduguanli.entity.User;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.ExpiredJwtException;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
import io.jsonwebtoken.io.Decoders;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.security.Key;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class JwtUtil {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
|
||||||
|
|
||||||
|
@Value("${jwt.secret}")
|
||||||
|
private String SECRET_KEY;
|
||||||
|
|
||||||
|
@Value("${jwt.expiration}")
|
||||||
|
private long jwtExpiration;
|
||||||
|
|
||||||
|
public String extractUsername(String token) {
|
||||||
|
logger.debug("开始从token中提取用户名");
|
||||||
|
try {
|
||||||
|
String username = extractClaim(token, Claims::getSubject);
|
||||||
|
logger.debug("提取的用户名: {}", username);
|
||||||
|
return username;
|
||||||
|
} catch (ExpiredJwtException e) {
|
||||||
|
logger.error("token已过期", e);
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("从token中提取用户名时发生错误", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date extractExpiration(String token) {
|
||||||
|
return extractClaim(token, Claims::getExpiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
|
||||||
|
final Claims claims = extractAllClaims(token);
|
||||||
|
return claimsResolver.apply(claims);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Claims extractAllClaims(String token) {
|
||||||
|
logger.debug("开始从token中提取所有声明");
|
||||||
|
try {
|
||||||
|
Claims claims = Jwts
|
||||||
|
.parserBuilder()
|
||||||
|
.setSigningKey(getSignKey())
|
||||||
|
.build()
|
||||||
|
.parseClaimsJws(token)
|
||||||
|
.getBody();
|
||||||
|
logger.debug("声明提取成功");
|
||||||
|
return claims;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("从token中提取声明时发生错误", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean isTokenExpired(String token) {
|
||||||
|
try {
|
||||||
|
Date expiration = extractExpiration(token);
|
||||||
|
boolean isExpired = expiration.before(new Date());
|
||||||
|
logger.debug("token过期检查 - 过期时间: {}, 是否过期: {}", expiration, isExpired);
|
||||||
|
return isExpired;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("检查token过期时发生错误", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateToken(String userName) {
|
||||||
|
logger.debug("开始为用户创建token: {}", userName);
|
||||||
|
try {
|
||||||
|
Map<String, Object> claims = new HashMap<>();
|
||||||
|
String token = Jwts.builder()
|
||||||
|
.setClaims(claims)
|
||||||
|
.setSubject(userName)
|
||||||
|
.setIssuedAt(new Date(System.currentTimeMillis()))
|
||||||
|
.setExpiration(new Date(System.currentTimeMillis() + jwtExpiration))
|
||||||
|
.signWith(getSignKey(), SignatureAlgorithm.HS384)
|
||||||
|
.compact();
|
||||||
|
logger.debug("token创建成功");
|
||||||
|
return token;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("创建token时发生错误", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean validateToken(String token, User user) {
|
||||||
|
logger.debug("开始验证用户token: {}", user.getUsername());
|
||||||
|
try {
|
||||||
|
final String username = extractUsername(token);
|
||||||
|
boolean isValid = username.equals(user.getUsername()) && !isTokenExpired(token);
|
||||||
|
logger.debug("token验证结果 - 用户名匹配: {}, 未过期: {}",
|
||||||
|
username.equals(user.getUsername()), !isTokenExpired(token));
|
||||||
|
return isValid;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("验证token时发生错误", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Key getSignKey() {
|
||||||
|
byte[] keyBytes = Decoders.BASE64.decode(SECRET_KEY);
|
||||||
|
return Keys.hmacShaKeyFor(keyBytes);
|
||||||
|
}
|
||||||
|
}
|
21
src/main/resources/application.properties
Normal file
21
src/main/resources/application.properties
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# 数据库配置
|
||||||
|
spring.datasource.url=jdbc:mysql://172.16.215.132:3306/fenshenzhike?useSSL=false&serverTimezone=UTC
|
||||||
|
spring.datasource.username=root
|
||||||
|
spring.datasource.password=123
|
||||||
|
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||||
|
|
||||||
|
server.port=1218
|
||||||
|
|
||||||
|
# JPA配置
|
||||||
|
spring.jpa.show-sql=true
|
||||||
|
spring.jpa.hibernate.ddl-auto=update
|
||||||
|
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
|
||||||
|
spring.jpa.properties.hibernate.format_sql=true
|
||||||
|
|
||||||
|
# JWT配置
|
||||||
|
jwt.secret=404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970
|
||||||
|
jwt.expiration=604800000
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
logging.level.org.springframework.security=DEBUG
|
||||||
|
logging.level.com.huertian.jinduguanli=DEBUG
|
48
src/main/resources/application.yml
Normal file
48
src/main/resources/application.yml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
server:
|
||||||
|
port: 1218
|
||||||
|
|
||||||
|
spring:
|
||||||
|
main:
|
||||||
|
banner-mode: console
|
||||||
|
datasource:
|
||||||
|
url: jdbc:mysql://172.16.215.132:3306/fenshenzhike?useSSL=false&serverTimezone=UTC
|
||||||
|
username: root
|
||||||
|
password: 123
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
jpa:
|
||||||
|
show-sql: true
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: update
|
||||||
|
properties:
|
||||||
|
hibernate:
|
||||||
|
format_sql: true
|
||||||
|
use_sql_comments: true
|
||||||
|
redis:
|
||||||
|
host: 172.16.215.132
|
||||||
|
port: 6379
|
||||||
|
database: 0
|
||||||
|
timeout: 10000
|
||||||
|
password: # 如果Redis设置了密码,需要在这里添加
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
max-active: 8
|
||||||
|
max-wait: -1ms
|
||||||
|
max-idle: 8
|
||||||
|
min-idle: 0
|
||||||
|
shutdown-timeout: 100ms
|
||||||
|
client-name: jinduguanli
|
||||||
|
connect-timeout: 5000
|
||||||
|
socket-timeout: 5000
|
||||||
|
client-type: lettuce
|
||||||
|
|
||||||
|
jwt:
|
||||||
|
secret: 404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970
|
||||||
|
expiration: 86400000 # 24小时
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
org.hibernate.SQL: DEBUG
|
||||||
|
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
|
||||||
|
com.huertian.jinduguanli: DEBUG
|
||||||
|
org.springframework.data.redis: DEBUG
|
||||||
|
io.lettuce.core: DEBUG
|
8
src/main/resources/banner.txt
Normal file
8
src/main/resources/banner.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
_________________
|
||||||
|
< Hi, 我是牛哥。。 >
|
||||||
|
-----------------
|
||||||
|
\ ^__^
|
||||||
|
\ (oo)\_______
|
||||||
|
(__)\ )\/\
|
||||||
|
||----w |
|
||||||
|
|| ||
|
BIN
src/test/.DS_Store
vendored
Normal file
BIN
src/test/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
src/test/java/.DS_Store
vendored
Normal file
BIN
src/test/java/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
src/test/java/com/.DS_Store
vendored
Normal file
BIN
src/test/java/com/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
src/test/java/com/huertian/.DS_Store
vendored
Normal file
BIN
src/test/java/com/huertian/.DS_Store
vendored
Normal file
Binary file not shown.
@ -0,0 +1,13 @@
|
|||||||
|
package com.huertian.jinduguanli;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class JinduguanliApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void contextLoads() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
145
数据库-新.md
Normal file
145
数据库-新.md
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
# 数据库设计文档
|
||||||
|
|
||||||
|
## 1. 部门表 (departments)
|
||||||
|
|
||||||
|
用于存储组织的部门信息。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE departments (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL COMMENT '部门名称',
|
||||||
|
description TEXT DEFAULT NULL COMMENT '部门描述',
|
||||||
|
created_at BIGINT NOT NULL COMMENT '创建时间(时间戳)',
|
||||||
|
updated_at BIGINT NOT NULL COMMENT '更新时间(时间戳)'
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='部门表';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 字段说明
|
||||||
|
- `id`: 部门ID,自增主键
|
||||||
|
- `name`: 部门名称,不可为空
|
||||||
|
- `description`: 部门描述,可为空
|
||||||
|
- `created_at`: 创建时间,毫秒级时间戳
|
||||||
|
- `updated_at`: 更新时间,毫秒级时间戳
|
||||||
|
|
||||||
|
## 2. 用户表 (users)
|
||||||
|
|
||||||
|
存储系统用户信息,包括教师、管理员等角色。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE users (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(50) NOT NULL COMMENT '用户名',
|
||||||
|
email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱',
|
||||||
|
password VARCHAR(100) NOT NULL COMMENT '密码',
|
||||||
|
department_id BIGINT NOT NULL COMMENT '所属部门',
|
||||||
|
roles INT NOT NULL COMMENT '角色: 1-教师, 2-普通管理员, 3-沟通联络人, 4-系统管理员',
|
||||||
|
jobs INT NOT NULL COMMENT '岗位: 1-课程制作教师, 2-课程购买方项目负责人, 3-课程制作方沟通联络人, 4-系统制作方项目负责人',
|
||||||
|
avatar VARCHAR(255) DEFAULT NULL COMMENT '头像',
|
||||||
|
creator_id BIGINT NOT NULL DEFAULT 1 COMMENT '创建用户的管理员ID',
|
||||||
|
status INT NOT NULL DEFAULT 1 COMMENT '用户状态: 1-正常, 0-禁用',
|
||||||
|
created_at BIGINT NOT NULL COMMENT '创建时间(时间戳)',
|
||||||
|
updated_at BIGINT NOT NULL COMMENT '更新时间(时间戳)',
|
||||||
|
FOREIGN KEY (department_id) REFERENCES departments (id) ON DELETE RESTRICT,
|
||||||
|
INDEX idx_users_department_id (department_id)
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='用户表';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 字段说明
|
||||||
|
- `id`: 用户ID,自增主键
|
||||||
|
- `username`: 用户名,不可为空
|
||||||
|
- `email`: 邮箱地址,不可为空,唯一索引
|
||||||
|
- `password`: 密码(加密存储),不可为空
|
||||||
|
- `department_id`: 所属部门ID,外键关联departments表
|
||||||
|
- `roles`: 用户角色,整数枚举:
|
||||||
|
- 1: 教师
|
||||||
|
- 2: 普通管理员
|
||||||
|
- 3: 沟通联络人
|
||||||
|
- 4: 系统管理员
|
||||||
|
- `jobs`: 用户岗位,整数枚举:
|
||||||
|
- 1: 课程制作教师
|
||||||
|
- 2: 课程购买方项目负责人
|
||||||
|
- 3: 课程制作方沟通联络人
|
||||||
|
- 4: 系统制作方项目负责人
|
||||||
|
- `avatar`: 用户头像URL,可为空
|
||||||
|
- `creator_id`: 创建该用户的管理员ID
|
||||||
|
- `status`: 用户状态:
|
||||||
|
- 1: 正常
|
||||||
|
- 0: 禁用
|
||||||
|
- `created_at`: 创建时间,毫秒级时间戳
|
||||||
|
- `updated_at`: 更新时间,毫秒级时间戳
|
||||||
|
|
||||||
|
### 索引
|
||||||
|
- 主键索引:`id`
|
||||||
|
- 外键索引:`idx_users_department_id (department_id)`
|
||||||
|
- 唯一索引:`email`
|
||||||
|
|
||||||
|
## 3. 课程任务表 (lesson_tasks)
|
||||||
|
|
||||||
|
存储课程制作任务的信息和进度。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE lesson_tasks (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
course_name VARCHAR(100) NOT NULL COMMENT '课程名称',
|
||||||
|
micro_lesson_name VARCHAR(100) NOT NULL COMMENT '微课名称',
|
||||||
|
user_id BIGINT NOT NULL COMMENT '负责人ID',
|
||||||
|
progress_status INT NOT NULL DEFAULT 0 COMMENT '当前任务进度状态: 0-脚本上传, 1-脚本确认, 2-视频拍摄, 3-后期制作, 4-任务完成',
|
||||||
|
script_upload_time BIGINT DEFAULT NULL COMMENT '脚本上传时间(时间戳)',
|
||||||
|
script_confirm_time BIGINT DEFAULT NULL COMMENT '脚本确认时间(时间戳)',
|
||||||
|
video_capture_time BIGINT DEFAULT NULL COMMENT '视频拍摄时间(时间戳)',
|
||||||
|
video_confirm_time BIGINT DEFAULT NULL COMMENT '视频确认时间(时间戳)',
|
||||||
|
finish_time BIGINT DEFAULT NULL COMMENT '任务完成时间(时间戳)',
|
||||||
|
advise TEXT DEFAULT NULL COMMENT '任务建议或备注',
|
||||||
|
created_at BIGINT NOT NULL COMMENT '创建时间(时间戳)',
|
||||||
|
updated_at BIGINT NOT NULL COMMENT '更新时间(时间戳)',
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
|
||||||
|
INDEX idx_lesson_tasks_user_id (user_id),
|
||||||
|
INDEX idx_lesson_tasks_progress_status (progress_status)
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='课程任务表';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 字段说明
|
||||||
|
- `id`: 任务ID,自增主键
|
||||||
|
- `course_name`: 课程名称,不可为空
|
||||||
|
- `micro_lesson_name`: 微课名称,不可为空
|
||||||
|
- `user_id`: 负责人ID,外键关联users表
|
||||||
|
- `progress_status`: 任务进度状态:
|
||||||
|
- 0: 脚本上传
|
||||||
|
- 1: 脚本确认
|
||||||
|
- 2: 视频拍摄
|
||||||
|
- 3: 后期制作
|
||||||
|
- 4: 任务完成
|
||||||
|
- `script_upload_time`: 脚本上传时间,毫秒级时间戳
|
||||||
|
- `script_confirm_time`: 脚本确认时间,毫秒级时间戳
|
||||||
|
- `video_capture_time`: 视频拍摄时间,毫秒级时间戳
|
||||||
|
- `video_confirm_time`: 视频确认时间,毫秒级时间戳
|
||||||
|
- `finish_time`: 任务完成时间,毫秒级时间戳
|
||||||
|
- `advise`: 任务相关的建议或备注,文本字段
|
||||||
|
- `created_at`: 创建时间,毫秒级时间戳
|
||||||
|
- `updated_at`: 更新时间,毫秒级时间戳
|
||||||
|
|
||||||
|
### 索引
|
||||||
|
- 主键索引:`id`
|
||||||
|
- 外键索引:`idx_lesson_tasks_user_id (user_id)`
|
||||||
|
- 普通索引:`idx_lesson_tasks_progress_status (progress_status)`
|
||||||
|
|
||||||
|
## 数据库关系
|
||||||
|
|
||||||
|
1. `users.department_id` -> `departments.id`
|
||||||
|
- 一个部门可以有多个用户
|
||||||
|
- 一个用户只能属于一个部门
|
||||||
|
- 使用RESTRICT约束,防止删除仍有用户的部门
|
||||||
|
|
||||||
|
2. `lesson_tasks.user_id` -> `users.id`
|
||||||
|
- 一个用户可以负责多个课程任务
|
||||||
|
- 一个课程任务只能有一个负责人
|
||||||
|
- 使用CASCADE约束,删除用户时自动删除其负责的课程任务
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 所有时间戳字段使用BIGINT类型,存储毫秒级时间戳
|
||||||
|
2. 字符编码统一使用utf8mb4,支持完整的Unicode字符集
|
||||||
|
3. 所有表都使用InnoDB引擎,支持事务和外键
|
||||||
|
4. 关键字段都建立了适当的索引以提高查询性能
|
||||||
|
5. 用户密码在存储前需要进行加密处理
|
||||||
|
6. 删除用户时会自动删除其关联的课程任务,但不会影响部门数据
|
313
设计需求.md
Normal file
313
设计需求.md
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
```
|
||||||
|
CREATE TABLE departments
|
||||||
|
(
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL COMMENT '部门名称',
|
||||||
|
description TEXT DEFAULT NULL COMMENT '部门描述',
|
||||||
|
created_at BIGINT NOT NULL COMMENT '创建时间(时间戳)',
|
||||||
|
updated_at BIGINT NOT NULL COMMENT '更新时间(时间戳)'
|
||||||
|
) ENGINE = InnoDB
|
||||||
|
DEFAULT CHARSET = utf8mb4 COMMENT ='部门表';
|
||||||
|
CREATE TABLE users
|
||||||
|
(
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
|
||||||
|
email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱',
|
||||||
|
password VARCHAR(100) NOT NULL COMMENT '密码',
|
||||||
|
department_id BIGINT NOT NULL COMMENT '所属部门',
|
||||||
|
roles INT NOT NULL COMMENT '角色: 1-教师, 2-普通管理员, 3-沟通联络人, 4-系统管理员',
|
||||||
|
jobs INT NOT NULL COMMENT '岗位: 1-课程制作教师, 2-课程购买方项目负责人, 3-课程制作方沟通联络人, 4-系统制作方项目负责人',
|
||||||
|
avatar VARCHAR(255) DEFAULT NULL COMMENT '头像',
|
||||||
|
creator_id BIGINT NOT NULL DEFAULT 1 COMMENT '创建用户的管理员ID',
|
||||||
|
status INT NOT NULL DEFAULT 1 COMMENT '用户状态: 1-正常, 2-禁用',
|
||||||
|
created_at BIGINT NOT NULL COMMENT '创建时间(时间戳)',
|
||||||
|
updated_at BIGINT NOT NULL COMMENT '更新时间(时间戳)',
|
||||||
|
FOREIGN KEY (department_id) REFERENCES departments (id) ON DELETE RESTRICT,
|
||||||
|
INDEX idx_users_department_id (department_id)
|
||||||
|
) ENGINE = InnoDB
|
||||||
|
DEFAULT CHARSET = utf8mb4 COMMENT ='用户表';
|
||||||
|
CREATE TABLE lesson_tasks
|
||||||
|
(
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
course_name VARCHAR(100) NOT NULL COMMENT '课程名称',
|
||||||
|
micro_lesson_name VARCHAR(100) NOT NULL COMMENT '微课名称',
|
||||||
|
user_id BIGINT NOT NULL COMMENT '负责人ID',
|
||||||
|
progress_status INT NOT NULL DEFAULT 0 COMMENT '当前任务进度状态: 0-脚本上传, 1-脚本确认, 2-视频拍摄, 3-后期制作, 4-任务完成',
|
||||||
|
script_upload_time BIGINT DEFAULT NULL COMMENT '脚本上传时间(时间戳)',
|
||||||
|
script_confirm_time BIGINT DEFAULT NULL COMMENT '脚本确认时间(时间戳)',
|
||||||
|
video_capture_time BIGINT DEFAULT NULL COMMENT '视频拍摄时间(时间戳)',
|
||||||
|
video_confirm_time BIGINT DEFAULT NULL COMMENT '视频确认时间(时间戳)',
|
||||||
|
finish_time BIGINT DEFAULT NULL COMMENT '任务完成时间(时间戳)',
|
||||||
|
advise TEXT DEFAULT NULL COMMENT '任务建议或备注',
|
||||||
|
created_at BIGINT NOT NULL COMMENT '创建时间(时间戳)',
|
||||||
|
updated_at BIGINT NOT NULL COMMENT '更新时间(时间戳)',
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
|
||||||
|
INDEX idx_lesson_tasks_user_id (user_id),
|
||||||
|
INDEX idx_lesson_tasks_progress_status (progress_status)
|
||||||
|
) ENGINE = InnoDB
|
||||||
|
DEFAULT CHARSET = utf8mb4 COMMENT ='课程任务表';
|
||||||
|
|
||||||
|
INSERT INTO departments (name, description, created_at, updated_at)
|
||||||
|
VALUES ('重庆眨生花科技有限公司', '重庆眨生花科技有限公司', NOW(), NOW()),
|
||||||
|
('重庆电子科技职业大学', '重庆电子科技职业大学', NOW(), NOW());
|
||||||
|
```
|
||||||
|
|
||||||
|
我修改了 我的表结构
|
||||||
|
|
||||||
|
接下来我将给你描述我的接口需求
|
||||||
|
|
||||||
|
**请求失败:**
|
||||||
|
|
||||||
|
返回错误状态码以:`10XX`开头。
|
||||||
|
|
||||||
|
错误代码`code`返回为 100XX。`message`为错误描述。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 10005,
|
||||||
|
"message": "邮箱已存在"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**请求成功:**
|
||||||
|
|
||||||
|
比失败请求会增加`data`字段。或直接返回最终结果。
|
||||||
|
|
||||||
|
1、登陆接口
|
||||||
|
|
||||||
|
## 用户管理
|
||||||
|
|
||||||
|
### 1.登录
|
||||||
|
|
||||||
|
接口描述: 使用密码登录
|
||||||
|
|
||||||
|
接口地址:http://127.0.0.1:8000/api/login
|
||||||
|
|
||||||
|
请求方式:POST
|
||||||
|
|
||||||
|
接口参数:
|
||||||
|
|
||||||
|
| 参数名字 | 类型 | 是否必须 | 默认值 | 其他 | 说明 |
|
||||||
|
| :------- | :----- | :------- | :----- | :--- | :---------------------------- |
|
||||||
|
| email | 整型 | 是 | | | 用户邮箱 |
|
||||||
|
| password | 字符串 | 是 | | | 密码 |
|
||||||
|
| remember | 布尔型 | 是 | false | | 会话 token 有效期,记住则更长 |
|
||||||
|
|
||||||
|
**返回结果**
|
||||||
|
|
||||||
|
成功示例:
|
||||||
|
|
||||||
|
| 参数名字 | 类型 | 说明 |
|
||||||
|
| -------- | ------ | ---- |
|
||||||
|
| token | 字符串 | |
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"token": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
失败示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 10005,
|
||||||
|
"message": "登录失败!请检查邮箱或者密码"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.添加用户
|
||||||
|
|
||||||
|
接口描述: 手动添加用户
|
||||||
|
|
||||||
|
接口地址:http://127.0.0.1:8000/api/users
|
||||||
|
|
||||||
|
请求方式:POST
|
||||||
|
|
||||||
|
**接口参数**
|
||||||
|
|
||||||
|
| 参数名字 | 类型 | 是否必须 | 默认值 | 其他 | 说明 |
|
||||||
|
| ------------- | ------ | -------- | ------ | ---- | ------ |
|
||||||
|
| username | 字符串 | 是 | | | 用户名 |
|
||||||
|
| email | 字符串 | 是 | | | 邮箱 |
|
||||||
|
| password | 字符串 | 是 | | | 密码 |
|
||||||
|
| roles | 整型 | 是 | | | 角色 |
|
||||||
|
| department_id | 整型 | 是 | | | 部门 |
|
||||||
|
| jobs | 整型 | 是 | | | 岗位 |
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "test2",
|
||||||
|
"email": "root2@cyqsd.cn",
|
||||||
|
"password": "Abc123456",
|
||||||
|
"roles": 4,
|
||||||
|
"jobs": 4,
|
||||||
|
"department_id": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**返回结果**
|
||||||
|
|
||||||
|
| 参数名字 | 类型 | 说明 |
|
||||||
|
| -------- | ------ | ------------------ |
|
||||||
|
| code | 整型 | 提示码 |
|
||||||
|
| message | 字符串 | 是否成功的提示信息 |
|
||||||
|
| data | 字符串 | 数据 ID |
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 10000,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": "3"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.查看用户列表
|
||||||
|
|
||||||
|
接口描述: 查看用户列表
|
||||||
|
|
||||||
|
接口地址:http://127.0.0.1:8000/api/users?page=1&limit=10
|
||||||
|
|
||||||
|
请求方式:GET
|
||||||
|
|
||||||
|
**接口参数**
|
||||||
|
|
||||||
|
| 参数名字 | 类型 | 是否必须 | 默认值 | 其他 | 说明 |
|
||||||
|
| -------- | ---- | -------- | ------ | ---- | ---- |
|
||||||
|
| page | 整型 | 是 | 1 | | |
|
||||||
|
| limit | 整型 | 是 | 10 | | |
|
||||||
|
|
||||||
|
### 4、查看单个用户
|
||||||
|
|
||||||
|
接口描述: 查看用户列表
|
||||||
|
|
||||||
|
接口地址:http://127.0.0.1:8000/api/users
|
||||||
|
|
||||||
|
请求方式:GET
|
||||||
|
|
||||||
|
| 参数名字 | 类型 | 是否必须 | 默认值 | 其他 | 说明 |
|
||||||
|
| -------- | ---- | -------- | ------ | ---- | ------- |
|
||||||
|
| id | 整型 | 是 | | | 用户 id |
|
||||||
|
|
||||||
|
### 5、删除用户
|
||||||
|
|
||||||
|
通过改变用户状态的方式 来将这个账户停止使用
|
||||||
|
|
||||||
|
将用户表的用户状态改变
|
||||||
|
|
||||||
|
### 6、更新账户
|
||||||
|
|
||||||
|
填了什么字段 就修改什么字段 没填写的字段 还是保留之前的数据
|
||||||
|
|
||||||
|
## 课程管理
|
||||||
|
|
||||||
|
### 1.查询列表
|
||||||
|
|
||||||
|
接口地址:http://127.0.0.1:8000/api/lesson/task?page=1&limit=1
|
||||||
|
|
||||||
|
接口描述: 查询课程管理列表。
|
||||||
|
|
||||||
|
请求方式:GET
|
||||||
|
|
||||||
|
**接口参数**
|
||||||
|
|
||||||
|
| 参数名字 | 类型 | 是否必须 | 默认值 | 其他 | 说明 |
|
||||||
|
| -------- | ---- | -------- | ------ | ----------------- | -------- |
|
||||||
|
| page | 整型 | 可选 | 1 | 最小:1 | 第几页 |
|
||||||
|
| perpage | 整型 | 可选 | 10 | 最小:1;最大:20 | 分页数量 |
|
||||||
|
| user_id | 整型 | 可选 | | | 用户 ID |
|
||||||
|
|
||||||
|
**返回结果**
|
||||||
|
|
||||||
|
| 参数名字 | 类型 | 说明 |
|
||||||
|
| ------------------- | ------ | -------- |
|
||||||
|
| course_name | 字符串 | 见下表 |
|
||||||
|
| m_lesson_name | 字符串 | |
|
||||||
|
| user_id | 整型 | |
|
||||||
|
| schedule_status | 整型 | |
|
||||||
|
| script_confirm_time | 整型 | |
|
||||||
|
| video_confirm_time | 整型 | |
|
||||||
|
| finish_time | 整型 | |
|
||||||
|
| script_upload_time | 整型 | |
|
||||||
|
| video_capture_time | 整型 | |
|
||||||
|
| script_file | 字符串 | |
|
||||||
|
| material_file | 字符串 | |
|
||||||
|
| capture_file | 字符串 | |
|
||||||
|
| advise | 字符串 | |
|
||||||
|
| created_at | 整型 | 创建时间 |
|
||||||
|
|
||||||
|
### 2.新增数据
|
||||||
|
|
||||||
|
接口地址:http://127.0.0.1:8000/api/lesson/task
|
||||||
|
|
||||||
|
接口描述: 新增一条课程记录
|
||||||
|
|
||||||
|
请求方式:POST
|
||||||
|
|
||||||
|
Content-Type:application/json
|
||||||
|
|
||||||
|
**接口参数**
|
||||||
|
|
||||||
|
| 参数名字 | 类型 | 是否必须 | 默认值 | 其他 | 说明 |
|
||||||
|
| ------------------- | ------ | -------- | --------- | ---- | ------------------------------ |
|
||||||
|
| course_name | 字符串 | 是 | 最大:25 | | 课程名 |
|
||||||
|
| m_lesson_name | 字符串 | 是 | 最大:25 | | 微课名 |
|
||||||
|
| user_id | 整型 | 是 | 最大:10 | | 用户 ID |
|
||||||
|
| schedule_status | 整型 | 否 | 最大:10 | | 当前进度 ID |
|
||||||
|
| script_confirm_time | 整型 | 否 | 最大:10 | | 脚本确认时间 |
|
||||||
|
| video_confirm_time | 整型 | 否 | 最大:10 | | 视频确认时间 |
|
||||||
|
| finish_time | 整型 | 否 | 最大:10 | | 任务完成时间 |
|
||||||
|
| script_upload_time | 整型 | 否 | 最大:10 | | 脚本上传时间 |
|
||||||
|
| video_capture_time | 整型 | 否 | 最大:10 | | 视频拍摄时间 |
|
||||||
|
| script_file | 字符串 | 否 | 最大:255 | | 脚本文件地址 |
|
||||||
|
| material_file | 字符串 | 否 | 最大:255 | | 素材文件地址 |
|
||||||
|
| capture_file | 字符串 | 否 | 最大:255 | | 拍摄结果文件地址 |
|
||||||
|
| advise | 字符串 | 否 | 最大:255 | | 沟通建议列表,自行构建保存格式 |
|
||||||
|
|
||||||
|
**返回结果**
|
||||||
|
|
||||||
|
| 参数名字 | 类型 | 说明 |
|
||||||
|
| -------- | ---- | ---- |
|
||||||
|
| | 整型 | ID |
|
||||||
|
|
||||||
|
### [#](https://aigc-doc.cqrthny.com/pages/ppmp/#_3-修改数据)3.修改数据
|
||||||
|
|
||||||
|
接口地址:http://127.0.0.1:8000/api/lesson/task/2
|
||||||
|
|
||||||
|
接口描述: 修改一条课程记录
|
||||||
|
|
||||||
|
请求方式:PUT
|
||||||
|
|
||||||
|
Content-Type:application/json
|
||||||
|
|
||||||
|
**接口参数**
|
||||||
|
|
||||||
|
其他需要修改的字段,按`新增数据`小节中的内容修改。
|
||||||
|
|
||||||
|
### [#](https://aigc-doc.cqrthny.com/pages/ppmp/#_4-删除数据)4.删除数据
|
||||||
|
|
||||||
|
接口地址:http://127.0.0.1:8000/api/lesson/task/1
|
||||||
|
|
||||||
|
接口描述: 删除一条课程记录
|
||||||
|
|
||||||
|
请求方式:DELETE
|
||||||
|
|
||||||
|
**接口参数**
|
||||||
|
|
||||||
|
| 参数名字 | 类型 | 是否必须 | 默认值 | 其他 | 说明 |
|
||||||
|
| -------- | ---- | -------- | ------ | ---- | ------------------- |
|
||||||
|
| 无 | 整型 | 是 | 否 | | 待删除的课程记录 ID |
|
||||||
|
|
||||||
|
### 5.查询单条数据
|
||||||
|
|
||||||
|
接口地址:http://127.0.0.1:8000/api/lesson/task/1
|
||||||
|
|
||||||
|
接口描述: 查询单条课程记录
|
||||||
|
|
||||||
|
请求方式:GET
|
||||||
|
|
||||||
|
**接口参数**
|
||||||
|
|
||||||
|
| 参数名字 | 类型 | 是否必须 | 默认值 | 其他 | 说明 |
|
||||||
|
| -------- | ---- | -------- | ------ | ---- | ---- |
|
||||||
|
| 无 | | | | | |
|
Loading…
Reference in New Issue
Block a user