92 Commits

Author SHA1 Message Date
4808960491 【优化】代码格式 2025-11-25 16:35:41 +08:00
74d8c66a6e 【新增】忘记密码接口 2025-11-25 16:35:11 +08:00
fca95e1203 【新增】封装账号类型校验工具 2025-11-25 16:34:19 +08:00
b7d3e07d16 【改进】账号类型校验 2025-11-25 16:33:56 +08:00
35ac70e82d 【修复】bug 2025-11-24 12:14:17 +08:00
632b37d5bd 【新增】完善后台首页面板 2025-11-21 18:39:51 +08:00
75ed266532 【新增】完善固件上传 2025-11-21 18:39:28 +08:00
884459e91a 【新增】完善固件上传 2025-11-19 09:47:45 +08:00
5acb3a34e0 【新增】OSS文件管理工具 2025-11-18 18:18:06 +08:00
9dc9bff8b1 【新增】飞行记录删除接口及数据同步 2025-11-18 18:17:44 +08:00
b44214f74a 【新增】上传飞行记录接口 2025-11-18 10:50:17 +08:00
78ff89ba0d 【修复】飞行记录接口 2025-11-18 09:33:10 +08:00
8bbb3ac38f 【新增】获取飞行记录接口 2025-11-17 18:28:20 +08:00
983f8cb429 【新增】隐私政策分类接口 2025-11-17 16:15:37 +08:00
0d9ae593c6 【改进】隐私政策分类 2025-11-17 16:15:26 +08:00
07e8151736 【优化】修改密码接口 2025-11-17 15:10:29 +08:00
ad5ca8c545 【新增】mongoDB支持 2025-11-17 15:10:09 +08:00
95e7b752e3 【改进】修改密码接口 2025-11-17 15:09:47 +08:00
3ea2777c94 【改进】隐私政策 2025-11-17 15:09:26 +08:00
1db7b3f6a1 【新增】排除咨询接口 2025-11-17 15:09:00 +08:00
03c5226a6a 【新增】咨询接口 2025-11-17 15:08:52 +08:00
7bd3d54d97 【新增】系统用户 2025-11-17 15:08:06 +08:00
61371a4a76 【改进】隐私政策 2025-11-13 16:27:46 +08:00
6606438862 【改进】密码校验 2025-11-12 09:26:59 +08:00
c9332b85db 【新增】增加跨域支持 2025-11-12 09:26:45 +08:00
74d19bb9d6 【新增】新增OSS-SDK 2025-11-12 09:26:35 +08:00
57f97a490a 【改进】固件上传 2025-11-12 09:26:16 +08:00
746e5051cf 【改进】完善教程 2025-11-12 09:25:35 +08:00
f27b57ec8b 【改进】完善教程详情页 2025-11-04 10:47:29 +08:00
5e0773a59f 【改进】更换接口 2025-11-04 10:47:15 +08:00
70ea5f4dd4 【改进】完善APP隐私协议政策 2025-11-04 10:46:57 +08:00
31ea86d90c 【新增】封装时间工具 2025-11-04 10:46:10 +08:00
73dda8b648 【新增】反馈管理 2025-11-04 10:06:20 +08:00
80a60e37af 【优化】页面组件优化,提升加载速度 2025-11-04 00:00:51 +08:00
71885c8796 【优化】固件管理 2025-11-03 23:23:51 +08:00
bfbb94481f 【改进】教程列表 2025-11-03 23:23:30 +08:00
3dcf9ca3ef 【新增】新增隐私协议 2025-11-03 23:22:25 +08:00
2b92f29a17 【优化】教程分类以及教程 2025-11-03 18:41:15 +08:00
3dbfff5f41 【新增】单行字体省略 2025-11-03 18:19:06 +08:00
f04385a306 【新增】教程指南管理 2025-11-03 18:18:37 +08:00
44f817eced 【优化】优化用户管理界面 2025-11-03 11:43:37 +08:00
0f2cc6a33c 【新增】完善固件管理 2025-11-03 11:43:11 +08:00
0d1127f25e 【新增】完善用户管理 2025-11-03 10:40:53 +08:00
bb7b4035fc 【改进】表格样式优化 2025-11-03 10:40:09 +08:00
c3b3ed3681 【新增】封装提示框 2025-11-03 10:39:46 +08:00
3fe027661b 【新增】封装提示模态框 2025-11-03 10:39:36 +08:00
74034ed26a 【改进】主页动态化 2025-10-31 17:39:24 +08:00
ad98033faa 【新增】系统用户界面 2025-10-31 17:39:05 +08:00
bd8f47d9ae 【优化】仪表盘 2025-10-31 17:38:33 +08:00
c975c205df 【改进】消息结果集 2025-10-31 17:38:17 +08:00
3ead596483 【新增】固件管理路由 2025-10-31 17:37:40 +08:00
882df50f75 【新增】仪表盘 2025-10-31 17:37:16 +08:00
13da68b90d 【新增】系统菜单控制器 2025-10-31 17:36:51 +08:00
5f8bc1af55 【新增】系统设置 2025-10-31 17:36:23 +08:00
1391239193 【新增】用户修改密码 2025-10-31 17:35:39 +08:00
fe93d9fac0 【新增】用户管理 2025-10-31 17:35:19 +08:00
6ed65f89b5 【改进】区分模块 2025-10-31 17:33:25 +08:00
9ff5b98b05 【改进】区分模块DTO 2025-10-31 17:32:54 +08:00
461c390cf7 【新增】分页参数拦截封装 2025-10-31 17:31:21 +08:00
1b9c9e9b86 【新增】分页参数拦截封装 2025-10-31 17:31:01 +08:00
be30f31990 【优化】拦截地址 2025-10-31 10:13:52 +08:00
ac4d7d8176 【改进】菜单动态化 2025-10-31 10:13:34 +08:00
54997d4e3d 【新增】实体基类 2025-10-31 10:13:13 +08:00
7bbb1be2a2 【新增】首页样式封装 2025-10-31 10:12:31 +08:00
19312e7a6f 【新增】系统菜单 2025-10-31 10:12:00 +08:00
727dd254f0 【新增】封装axios 2025-10-31 10:11:33 +08:00
2942fdabfe 【优化】优化政策协议接口 2025-10-30 16:05:23 +08:00
5ea30d6f85 【新增】排除隐私政策接口拦截 2025-10-30 12:29:30 +08:00
e6ae7d60e3 【新增】隐私政策 2025-10-30 12:29:03 +08:00
cca1847fbf 【优化】路径 2025-10-30 10:27:52 +08:00
10a317bed6 【新增】接口注释 2025-10-30 10:20:39 +08:00
a3bf673440 Merge branch 'main' of http://120.24.204.180:3000/admin/core_wing_web 2025-10-30 09:55:37 +08:00
30c4ec612a Merge remote-tracking branch 'origin/main' 2025-10-30 09:50:14 +08:00
486455af81 Merge branch 'dev_20251030' 2025-10-30 09:49:24 +08:00
e8dc76ef82 【新增】新增显示教程详情
Some checks failed
CI Build and Test / build (pull_request) Has been cancelled
2025-10-30 09:36:39 +08:00
8aa6fc0abf 【优化】拦截地址 2025-10-30 09:36:09 +08:00
7eda1a85eb 【优化】使用Thymeleaf渲染 2025-10-30 09:35:46 +08:00
6a124a5754 【删除】教程展示测试文件 2025-10-30 09:34:03 +08:00
564e617802 【改进】模块化 2025-10-30 09:32:55 +08:00
3f09aa1238 【新增】模板引擎支持包 2025-10-30 09:31:24 +08:00
b6cdbba20a Merge pull request 'dev_20251029' (#3) from dev_20251029 into main
Reviewed-on: #3
2025-10-29 10:30:41 +00:00
bc77cd244b Merge pull request #5
dev_20251029
2025-10-29 18:26:53 +08:00
5306431417 Merge pull request #4
dev_20251029
2025-10-29 18:15:44 +08:00
53287f0a1d Merge pull request #3
dev_20251029
2025-10-29 18:12:57 +08:00
efbe2a3def Merge pull request 'dev_20251029' (#2) from dev_20251029 into main
Reviewed-on: #2
2025-10-29 08:59:47 +00:00
a1cbb9fdf0 Merge pull request #2 from MakeSomeFakeNews/dev_20251029
dev_20251029
2025-10-29 16:57:06 +08:00
a84e8b9fe6 Merge pull request '【优化】优化自增id' (#1) from dev_20251028 into main
Reviewed-on: #1
2025-10-28 08:39:06 +00:00
98052c0ce7 删除 .gitea/workflows/deploy.yml 2025-10-28 07:44:22 +00:00
3780b9d2ab 删除 .gitea/workflows/ci.yml
Some checks failed
Deploy to Server / build-and-deploy (push) Has been cancelled
2025-10-28 07:44:18 +00:00
bd43d35074 删除 .gitea/workflows/README.md
Some checks failed
Deploy to Server / build-and-deploy (push) Has been cancelled
CI Build and Test / build (push) Has been cancelled
2025-10-28 07:44:03 +00:00
085cd485ad 新增后台管理
Some checks failed
CI Build and Test / build (push) Has been cancelled
Deploy to Server / build-and-deploy (push) Has been cancelled
2025-10-28 15:40:06 +08:00
9007c7de57 Merge pull request #1 from MakeSomeFakeNews/dev_20251028
Dev 20251028
2025-10-28 15:37:05 +08:00
120 changed files with 8747 additions and 949 deletions

View File

@@ -1,211 +0,0 @@
# Gitea Actions 工作流配置说明
本项目配置了两个 Gitea Actions 工作流,用于自动化构建、测试和部署。
## 工作流列表
### 1. ci.yml - 持续集成
**触发时机:**
- 推送代码到 `main``master` 分支
- 创建 Pull Request
**执行内容:**
- 自动检出代码
- 配置 Java 8 环境
- 使用 Gradle 构建项目
- 运行单元测试
- 上传构建产物JAR 包)
- 上传测试报告
**查看结果:**
构建完成后,可以在 Gitea 仓库的 "Actions" 标签页查看运行结果和下载构建产物。
---
### 2. deploy.yml - 自动部署
**触发时机:**
- 推送代码到 `main``master` 分支
- 手动触发(在 Gitea Actions 页面点击 "Run workflow"
**执行内容:**
- 构建项目
- 通过 SCP 将 JAR 包上传到服务器
- 通过 SSH 重启应用
**配置步骤:**
#### 第一步:在 Gitea 中配置 Secrets
进入仓库 → Settings → Secrets添加以下密钥
| 密钥名称 | 说明 | 示例 |
|---------|------|------|
| `SERVER_HOST` | 服务器 IP 地址 | `120.24.204.180` |
| `SERVER_PORT` | SSH 端口 | `22` |
| `SERVER_USER` | SSH 用户名 | `root``ubuntu` |
| `SERVER_SSH_KEY` | SSH 私钥内容 | 完整的私钥文件内容 |
| `DEPLOY_PATH` | 部署目录路径 | `/opt/corewing` |
#### 第二步:生成 SSH 密钥对(如果还没有)
```bash
# 在本地生成密钥对
ssh-keygen -t rsa -b 4096 -C "gitea-deploy" -f ~/.ssh/gitea_deploy
# 查看公钥
cat ~/.ssh/gitea_deploy.pub
# 查看私钥(复制到 Gitea Secrets 中)
cat ~/.ssh/gitea_deploy
```
#### 第三步:配置服务器
```bash
# 1. SSH 登录到服务器
ssh user@your-server
# 2. 创建部署目录
sudo mkdir -p /opt/corewing
sudo chown $USER:$USER /opt/corewing
# 3. 添加公钥到 authorized_keys
echo "your-public-key-content" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
# 4. 安装 Java如果未安装
sudo apt update
sudo apt install openjdk-8-jdk -y
java -version
```
#### 第四步:测试部署
推送代码到 main 分支,或在 Gitea Actions 页面手动触发 deploy 工作流。
---
## 启用 Gitea Actions
### Gitea 服务器端配置
如果你的 Gitea 实例还未启用 Actions需要管理员配置
1. **修改 Gitea 配置文件** (`app.ini`)
```ini
[actions]
ENABLED = true
DEFAULT_ACTIONS_URL = https://gitea.com
```
2. **安装 Gitea Act Runner**
```bash
# 下载 Act Runner
wget https://dl.gitea.com/act_runner/0.2.6/act_runner-0.2.6-linux-amd64
chmod +x act_runner-0.2.6-linux-amd64
mv act_runner-0.2.6-linux-amd64 /usr/local/bin/act_runner
# 注册 Runner
act_runner register --no-interactive --instance http://120.24.204.180:3000 --token YOUR_RUNNER_TOKEN
# 启动 Runner
act_runner daemon
```
3. **获取 Runner Token**
- 登录 Gitea 管理后台
- 进入 Site Administration → Actions → Runners
- 点击 "Create new Runner" 获取 Token
---
## 自定义工作流
### 修改触发条件
可以根据需要修改工作流的触发条件:
```yaml
on:
push:
branches: [ main, develop ] # 监听多个分支
tags:
- 'v*' # 监听标签
schedule:
- cron: '0 0 * * *' # 定时执行(每天午夜)
```
### 添加环境变量
在工作流中使用环境变量:
```yaml
env:
JAVA_VERSION: '8'
SPRING_PROFILES_ACTIVE: 'prod'
steps:
- name: 运行应用
run: java -jar -Dspring.profiles.active=${{ env.SPRING_PROFILES_ACTIVE }} app.jar
```
### 添加通知
可以添加构建成功/失败通知:
```yaml
- name: 发送通知
if: failure()
run: |
curl -X POST "https://your-notification-webhook" \
-d "构建失败: ${{ github.repository }}"
```
---
## 常见问题
### 1. 工作流未触发?
- 检查 Gitea Actions 是否启用
- 检查 Act Runner 是否正常运行
- 确认触发条件是否匹配
### 2. 构建失败?
- 查看 Actions 日志获取详细错误信息
- 检查 Java 版本是否正确
- 确认依赖是否能正常下载
### 3. 部署失败?
- 检查 Secrets 配置是否正确
- 测试 SSH 连接是否正常
- 检查服务器目录权限
### 4. 如何查看日志?
- Gitea 仓库 → Actions 标签页
- 点击具体的工作流运行记录
- 展开各个步骤查看详细日志
---
## 最佳实践
1. **分支策略**
- `main` 分支自动部署到生产环境
- `develop` 分支自动部署到测试环境
- Pull Request 只执行构建和测试
2. **安全性**
- 不要在代码中硬编码密码和密钥
- 使用 Secrets 管理敏感信息
- 定期轮换 SSH 密钥
3. **性能优化**
- 启用 Gradle 缓存加快构建速度
- 使用 `build -x test` 跳过测试快速构建
- 合理设置产物保留时间
4. **监控**
- 定期检查工作流运行状态
- 设置构建失败通知
- 保存构建日志便于问题排查
---
## 更多资源
- [Gitea Actions 官方文档](https://docs.gitea.io/en-us/actions/)
- [GitHub Actions 语法参考](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions)Gitea Actions 兼容 GitHub Actions 语法)
- [Act Runner 项目](https://gitea.com/gitea/act_runner)

View File

@@ -1,55 +0,0 @@
name: CI Build and Test
# 触发条件:推送到 main 分支或创建 Pull Request
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
# 1. 检出代码
- name: 检出代码
uses: actions/checkout@v3
# 2. 设置 Java 环境
- name: 设置 Java 8
uses: actions/setup-java@v3
with:
java-version: '8'
distribution: 'temurin'
cache: 'gradle'
# 3. 赋予 Gradle wrapper 执行权限
- name: 赋予 Gradle wrapper 执行权限
run: chmod +x gradlew
# 4. 构建项目(跳过测试)
- name: 使用 Gradle 构建项目
run: ./gradlew build -x test
# 5. 运行测试
- name: 运行测试
run: ./gradlew test
# 6. 上传构建产物JAR 文件)
- name: 上传 JAR 包
uses: actions/upload-artifact@v3
if: success()
with:
name: corewing-app
path: build/libs/*.jar
retention-days: 7
# 7. 上传测试报告
- name: 上传测试报告
uses: actions/upload-artifact@v3
if: always()
with:
name: test-reports
path: build/reports/tests/
retention-days: 7

View File

@@ -1,80 +0,0 @@
name: Deploy to Server
# 触发条件:手动触发或推送到 main 分支
on:
push:
branches: [ main, master ]
workflow_dispatch: # 允许手动触发
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
# 1. 检出代码
- name: 检出代码
uses: actions/checkout@v3
# 2. 设置 Java 环境
- name: 设置 Java 8
uses: actions/setup-java@v3
with:
java-version: '8'
distribution: 'temurin'
cache: 'gradle'
# 3. 赋予 Gradle wrapper 执行权限
- name: 赋予 Gradle wrapper 执行权限
run: chmod +x gradlew
# 4. 构建项目
- name: 构建项目
run: ./gradlew build -x test
# 5. 打包 JAR 文件
- name: 获取 JAR 文件名
id: jar
run: echo "jar_file=$(ls build/libs/*.jar | head -n 1)" >> $GITHUB_OUTPUT
# 6. 部署到服务器(使用 SCP
# 需要在 Gitea 仓库设置中配置以下 Secrets
# - SERVER_HOST: 服务器地址
# - SERVER_PORT: SSH 端口(默认 22
# - SERVER_USER: SSH 用户名
# - SERVER_SSH_KEY: SSH 私钥
# - DEPLOY_PATH: 部署路径(如 /opt/corewing
- name: 部署到服务器
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
port: ${{ secrets.SERVER_PORT }}
source: "build/libs/*.jar"
target: ${{ secrets.DEPLOY_PATH }}
strip_components: 2
# 7. 重启应用(通过 SSH 执行命令)
- name: 重启应用
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
port: ${{ secrets.SERVER_PORT }}
script: |
cd ${{ secrets.DEPLOY_PATH }}
# 停止旧进程
pkill -f corewing || true
# 等待进程完全停止
sleep 3
# 启动新进程
nohup java -jar *.jar > app.log 2>&1 &
# 检查启动状态
sleep 5
if pgrep -f corewing > /dev/null; then
echo "应用启动成功"
else
echo "应用启动失败"
exit 1
fi

View File

@@ -36,11 +36,14 @@ dependencies {
implementation 'cn.dev33:sa-token-spring-boot-starter:1.44.0' // 权限认证
implementation 'com.alibaba:druid-spring-boot-starter:1.2.27' // 数据库连接池
implementation 'org.lionsoul:ip2region:2.7.0' // IP 归属地
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' // mongodb
compileOnly 'org.projectlombok:lombok' // Lombok
developmentOnly 'org.springframework.boot:spring-boot-devtools' // 热重载
runtimeOnly 'com.mysql:mysql-connector-j' // MySQL 驱动
annotationProcessor 'org.projectlombok:lombok' // Lombok 注解处理
testImplementation 'org.springframework.boot:spring-boot-starter-test' // 测试框架
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // thymeleaf模版引擎
implementation 'com.aliyun.oss:aliyun-sdk-oss:3.18.1' // OSS SDK
}
tasks.named('test') {

View File

@@ -54,6 +54,13 @@ public class Result<T> implements Serializable {
return new Result<>(200, I18nUtil.getMessage("common.success"), data, true);
}
/**
* 成功返回(自定义消息)
*/
public static <T> Result<T> success(String message) {
return new Result<>(200, message, null, true);
}
/**
* 成功返回(自定义消息和数据)
*/
@@ -81,4 +88,27 @@ public class Result<T> implements Serializable {
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null, false);
}
/**
* 根据状态返回信息结果
*/
public static <T> Result<T> isBool(boolean flag) {
return flag ? Result.success() : Result.error();
}
/**
* 根据状态返回信息结果(自定义成功消息)
*/
public static <T> Result<T> isBoolAsMsg(boolean flag, String successMsg) {
return flag ? Result.success(successMsg) : Result.error();
}
/**
* 根据状态返回信息结果(自定义消息)
*/
public static <T> Result<T> isBoolAsMsg(boolean flag, String successMsg, String errorMsg) {
return flag ? Result.success(successMsg) : Result.error(errorMsg);
}
}

View File

@@ -0,0 +1,37 @@
package com.corewing.app.common.base;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Date;
@Data
public class BaseEntity {
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 创建人
*/
private String createBy;
/**
* 修改时间
*/
@TableField(fill = FieldFill.UPDATE)
private LocalDateTime updateTime;
/**
* 修改人
*/
private String updateBy;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,23 @@
package com.corewing.app.common.page;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
public class PageContext {
private static final ThreadLocal<Page<?>> PAGE_HOLDER = new ThreadLocal<>();
// 设置分页对象
public static void setPage(Page<?> page) {
PAGE_HOLDER.set(page);
}
// 泛型方法:获取指定类型的分页对象(消除警告)
@SuppressWarnings("unchecked")
public static <T> Page<T> getPage(Class<T> clazz) {
return (Page<T>) PAGE_HOLDER.get();
}
// 清除线程变量
public static void clear() {
PAGE_HOLDER.remove();
}
}

View File

@@ -0,0 +1,53 @@
package com.corewing.app.common.page;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class PageInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 默认分页参数
int pageNum = 1;
int pageSize = 10;
// 从请求参数中获取分页信息
String current = request.getParameter("current");
String size = request.getParameter("size");
// 解析页码
if (current != null && !current.isEmpty()) {
try {
pageNum = Integer.parseInt(current);
if (pageNum < 1) pageNum = 1; // 页码不能小于1
} catch (NumberFormatException e) {
// 非法参数使用默认值
}
}
// 解析每页条数
if (size != null && !size.isEmpty()) {
try {
pageSize = Integer.parseInt(size);
if (pageSize < 1) pageSize = 10; // 每页条数不能小于1
if (pageSize > 100) pageSize = 100; // 限制最大条数
} catch (NumberFormatException e) {
// 非法参数使用默认值
}
}
// 创建 MyBatis-Plus 的 Page 对象并存储到 ThreadLocal
Page<?> page = new Page<>(pageNum, pageSize);
PageContext.setPage(page);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清除ThreadLocal中的数据防止内存泄漏
PageContext.clear();
}
}

View File

@@ -0,0 +1,39 @@
package com.corewing.app.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration // 标记为配置类
public class CorsConfig implements WebMvcConfigurer {
/**
* 配置跨域过滤器,优先级高于所有拦截器
*/
@Bean
public CorsFilter corsFilter() {
// 1. 配置跨域参数
CorsConfiguration config = new CorsConfiguration();
// 允许的前端域名(根据实际环境修改)
config.addAllowedOriginPattern("*");
// 允许携带 Cookie前后端都需要开启
config.setAllowCredentials(true);
// 允许的请求方法(包含预检请求 OPTIONS
config.addAllowedMethod("*");
// 允许的请求头(* 表示所有)
config.addAllowedHeader("*");
// 预检请求缓存时间1小时减少重复验证
config.setMaxAge(3600L);
// 2. 配置路径匹配规则(对所有接口生效)
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
// 3. 返回过滤器(优先级最高)
return new CorsFilter(source);
}
}

View File

@@ -2,6 +2,7 @@ package com.corewing.app.config;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import com.corewing.app.common.page.PageInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -17,27 +18,35 @@ public class SaTokenConfig implements WebMvcConfigurer {
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 分页参数拦截
registry.addInterceptor(new PageInterceptor());
// 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
// 拦截所有路由
.addPathPatterns("/**")
// 排除登录、注册、发送验证码接口
.excludePathPatterns("/user/login", "/user/register", "/user/sendCode")
// 排除登录、注册、发送验证码, 忘记密码接口
.excludePathPatterns("/user/login", "/user/register", "/user/sendCode", "/user/forgetPassword")
// 排除后台管理登录接口
.excludePathPatterns("/sys/user/login")
// 排除反馈接口(支持匿名提交)
.excludePathPatterns("/feedback", "/feedback/**")
// 排除教程接口(支持匿名查询)
.excludePathPatterns("/tutorial", "/tutorial/**")
// 排除隐私政策接口(支持匿名查询)
.excludePathPatterns("/privacy_policy", "/privacy_policy/**")
// 排除固件查询接口(不需要登录)
.excludePathPatterns("/firmware/**")
// 排除系统登录页(不需要登录)
.excludePathPatterns("/loading.html", "/admin/login.html")
// 排除静态资源
.excludePathPatterns("/", "/index.html", "/admin/login.html", "/*.css", "/*.js", "/*.ico", "/static/**")
.excludePathPatterns("/", "/*.css", "/*.js", "/*.ico", "/static/**", "/assets/**")
// 排除后台管理静态资源
.excludePathPatterns("/admin/**")
// 排除 Druid 监控
.excludePathPatterns("/druid/**")
// 排除错误页面
.excludePathPatterns("/error", "/error/**");
.excludePathPatterns("/error", "/error/**")
// 排除咨询接口
.excludePathPatterns("/contactMsg", "/contactMsg/**");
}
}

View File

@@ -1,4 +1,4 @@
package com.corewing.app.dto;
package com.corewing.app.dto.api;
import lombok.Data;

View File

@@ -1,4 +1,4 @@
package com.corewing.app.dto;
package com.corewing.app.dto.api;
import lombok.Data;

View File

@@ -0,0 +1,19 @@
package com.corewing.app.dto.api;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class ForgetPasswordRequest {
@NotBlank(message = "手机号或邮箱不能为空")
private String account;
@NotBlank(message = "验证码不能为空")
private String verificationCode;
@NotBlank(message = "密码不能为空")
private String password;
}

View File

@@ -1,4 +1,4 @@
package com.corewing.app.dto;
package com.corewing.app.dto.api;
import lombok.Data;

View File

@@ -1,4 +1,4 @@
package com.corewing.app.dto;
package com.corewing.app.dto.api;
import lombok.Data;

View File

@@ -1,4 +1,4 @@
package com.corewing.app.dto;
package com.corewing.app.dto.api;
import lombok.Data;
@@ -17,7 +17,7 @@ public class SendCodeRequest {
private String account;
/**
* 验证码类型register-注册, login-登录, reset-重置密码
* 验证码类型register-注册, login-登录, reset-重置密码, forget-忘记密码
*/
@NotBlank(message = "验证码类型不能为空")
private String type;

View File

@@ -1,4 +1,4 @@
package com.corewing.app.dto;
package com.corewing.app.dto.api;
import lombok.Data;

View File

@@ -0,0 +1,47 @@
package com.corewing.app.dto.api;
import lombok.Data;
@Data
public class TrajectoryData {
private Long id;
private Long session_id;
private Long timestamp;
// 基础遥测数据
private Long rc;
private Long gps;
private Long gps_status;
private double voltage;
private double cell_voltage;
private Long consumption;
private double current;
private double speed;
private double air_speed;
private double v_speed;
private double altitude;
private double distance;
private double range;
private Long throttle;
private Long wind_direction;
private double wind_speed;
private Long heading;
private double roll;
private double pitch;
private double yaw;
// 扩展数据
private double compass_heading;
private double drone_lat;
private double drone_lon;
private double home_lat;
private double home_lon;
private String map_type;
private Boolean show_trajectory;
private String flight_mode;
private Long vehicle_type;
private Long autopilot_type;
private Boolean is_armed;
private String sync_id;
}

View File

@@ -0,0 +1,7 @@
package com.corewing.app.dto.api;
import lombok.Data;
@Data
public class TutorialListRequest {
}

View File

@@ -1,4 +1,4 @@
package com.corewing.app.dto;
package com.corewing.app.dto.api;
import lombok.Data;

View File

@@ -1,4 +1,4 @@
package com.corewing.app.dto;
package com.corewing.app.dto.api;
import lombok.Data;

View File

@@ -0,0 +1,26 @@
package com.corewing.app.dto.api;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
public class uploadReplaySessionRequest {
private Long id;
private String name;
private String description;
private Long start_time;
private Long end_time;
private Long duration; // 持续时间(秒)
private Long data_count; // 数据点数量
private Date created_at;
private Date updated_at;
private String sync_id;
List<TrajectoryData> trajectoryDataList;
}

View File

@@ -0,0 +1,11 @@
package com.corewing.app.dto.biz.feedback;
import lombok.Data;
import java.util.List;
@Data
public class FeedbackBatchDeleteRequest {
private List<Long> ids;
}

View File

@@ -0,0 +1,13 @@
package com.corewing.app.dto.biz.feedback;
import lombok.Data;
import java.util.List;
@Data
public class FeedbackBatchStatusRequest {
private List<Long> ids;
private Integer status;
}

View File

@@ -0,0 +1,10 @@
package com.corewing.app.dto.biz.firmware;
import lombok.Data;
import java.util.List;
@Data
public class FirmwareBatchDeleteRequest {
private List<Long> ids;
}

View File

@@ -0,0 +1,11 @@
package com.corewing.app.dto.biz.privacyPolicy;
import lombok.Data;
import java.util.List;
@Data
public class PrivacyPolicyBatchDeleteRequest {
private List<Long> ids;
}

View File

@@ -0,0 +1,10 @@
package com.corewing.app.dto.biz.tutorial;
import lombok.Data;
import java.util.List;
@Data
public class BatchTutorialDeleteRequest {
private List<Long> ids;
}

View File

@@ -0,0 +1,10 @@
package com.corewing.app.dto.biz.tutorial;
import lombok.Data;
import java.util.List;
@Data
public class CategoryBatchDeleteRequest {
private List<Long> ids;
}

View File

@@ -0,0 +1,12 @@
package com.corewing.app.dto.biz.user;
import lombok.Data;
import java.util.List;
@Data
public class BizUserBatchDeleteRequest {
private List<Long> ids;
}

View File

@@ -0,0 +1,8 @@
package com.corewing.app.dto.biz.user;
import lombok.Data;
@Data
public class BizUserIdRequest {
private long id;
}

View File

@@ -0,0 +1,11 @@
package com.corewing.app.dto.biz.user;
import lombok.Data;
import java.util.List;
@Data
public class BizUserStatusRequest {
private List<Long> ids;
private Integer status;
}

View File

@@ -0,0 +1,18 @@
package com.corewing.app.dto.biz.user;
import lombok.Data;
@Data
public class ResetPasswordRequest {
/**
* 用户id
*/
private Integer userId;
/**
* 新密码
*/
private String password;
}

View File

@@ -0,0 +1,9 @@
package com.corewing.app.dto.sys;
import lombok.Data;
@Data
public class SysResetPasswordRequest {
private Long userId;
private String password;
}

View File

@@ -0,0 +1,8 @@
package com.corewing.app.dto.sys;
import lombok.Data;
@Data
public class SysUserIdRequest {
private Long id;
}

View File

@@ -0,0 +1,52 @@
package com.corewing.app.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.corewing.app.common.base.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.data.annotation.Id;
/**
* 联系消息
*/
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("app_contact_msg")
public class ContactMsg extends BaseEntity {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 咨询人
*/
private String name;
/**
* 咨询类型
*/
private int category;
/**
* 联系方式
*/
private String contact;
/**
* 咨询内容
*/
private String msg;
/**
* 咨询状态
*/
private int status;
}

View File

@@ -66,4 +66,34 @@ public class Feedback implements Serializable {
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 用户昵称
*/
@TableField(exist = false)
private String nickName;
/**
* 用户名
*/
@TableField(exist = false)
private String username;
@TableField(exist = false)
private String statusName;
public String getStatusName() {
switch (status) {
case 0:
return "待处理";
case 1:
return "处理中";
case 2:
return "已完成";
case 3:
return "已关闭";
default:
return "";
}
}
}

View File

@@ -61,4 +61,20 @@ public class Firmware implements Serializable {
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(exist = false)
private String firmwareTypeName;
public String getFirmwareTypeName() {
switch (firmwareType) {
case 1:
return "调参固件";
case 2:
return "AP固件";
case 3:
return "INAV固件";
default:
return "";
}
}
}

View File

@@ -0,0 +1,48 @@
package com.corewing.app.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.corewing.app.common.base.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 隐私政策实体
*/
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("app_privacy_policy")
public class PrivacyPolicy extends BaseEntity {
/**
* 隐私政策id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 隐私政策名称
*/
private String title;
/**
* 隐私政策内容
*/
private String content;
/**
* 是否显示
*/
private Integer visible;
/**
* 排序
*/
private Integer sort;
/**
* 类型
*/
private Integer category;
}

View File

@@ -0,0 +1,80 @@
package com.corewing.app.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.corewing.app.dto.api.TrajectoryData;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
@TableName("app_replay_session")
public class ReplaySession {
/**
* id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String syncId;
/**
* 用户id
*/
private Long userId;
/**
* 名称
*/
private String name;
/**
* oss地址
*/
private String ossPath;
/**
* 描述
*/
private String description;
/**
* 开始时间戳
*/
private Long startTime;
/**
* 结束时间戳
*/
private Long endTime;
/**
* 持续时间
*/
private Long duration;
/**
* 数据点
*/
private Long dataCount;
/**
* 创建时间
*/
private Date createdAt;
/**
* 更新时间
*/
private Date updatedAt;
/**
* 轨迹数据点
*/
@TableField(exist = false)
List<TrajectoryData> trajectoryDataList;
}

View File

@@ -0,0 +1,57 @@
package com.corewing.app.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.corewing.app.common.base.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("sys_menu")
public class SysMenu extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 菜单id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 菜单名称
*/
private String menuName;
/**
* 菜单地址
*/
private String menuUrl;
/**
* 菜单图标
*/
private String menuIcon;
/**
* 菜单分类
*/
private String menuCategory;
/**
* 是否隐藏
*/
private boolean visible;
/**
* 排序
*/
private int sort;
}

View File

@@ -0,0 +1,31 @@
package com.corewing.app.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.corewing.app.common.base.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("sys_option")
public class SysOption extends BaseEntity {
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 键名
*/
private String key;
/**
* 键值
*/
private String value;
}

View File

@@ -1,8 +1,6 @@
package com.corewing.app.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -62,12 +60,23 @@ public class Tutorial implements Serializable {
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.UPDATE)
private Date updateTime;
/**
* 教程分类id
*/
@TableField(exist = false)
private Long categoryId;
@TableField(exist = false)
private String categoryName;
}

View File

@@ -1,13 +1,13 @@
package com.corewing.app.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
/**
* 教程分类
@@ -65,11 +65,19 @@ public class TutorialCategory implements Serializable {
/**
* 创建时间
*/
private Date createTime;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
private Date updateTime;
@TableField(fill = FieldFill.UPDATE)
private LocalDateTime updateTime;
public static final String typeCategory = "category";
public static final String typeTag = "tag";
@TableField(exist = false)
private List<Tutorial> tutorials;
}

View File

@@ -25,6 +25,11 @@ public class User implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户昵称
*/
private String nickName;
/**
* 用户名
*/

View File

@@ -0,0 +1,10 @@
package com.corewing.app.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.corewing.app.entity.ContactMsg;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ContactMsgMapper extends BaseMapper<ContactMsg> {
}

View File

@@ -1,8 +1,10 @@
package com.corewing.app.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.corewing.app.entity.Feedback;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 问题反馈 Mapper 接口
@@ -10,4 +12,5 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface FeedbackMapper extends BaseMapper<Feedback> {
Page<Feedback> page(Page<Feedback> page, @Param("feedback") Feedback feedback);
}

View File

@@ -0,0 +1,10 @@
package com.corewing.app.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.corewing.app.entity.PrivacyPolicy;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PrivacyPolicyMapper extends BaseMapper<PrivacyPolicy> {
}

View File

@@ -0,0 +1,9 @@
package com.corewing.app.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.corewing.app.entity.ReplaySession;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ReplaySessionMapper extends BaseMapper<ReplaySession> {
}

View File

@@ -0,0 +1,9 @@
package com.corewing.app.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.corewing.app.entity.SysMenu;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysMenuMapper extends BaseMapper<SysMenu> {
}

View File

@@ -0,0 +1,9 @@
package com.corewing.app.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.corewing.app.entity.SysOption;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysOptionMapper extends BaseMapper<SysOption> {
}

View File

@@ -13,5 +13,7 @@ import org.apache.ibatis.annotations.Param;
@Mapper
public interface TutorialMapper extends BaseMapper<Tutorial> {
Page<Tutorial> pageList(Page<Tutorial> page, @Param("categoryId") int categoryId, @Param("tutorialTitle") String tutorialTitle, @Param("lang") String lang);
Page<Tutorial> pageList(Page<Tutorial> page, @Param("categoryId") Long categoryId, @Param("tutorialTitle") String tutorialTitle, @Param("lang") String lang);
Page<Tutorial> page(Page<Tutorial> page, @Param("tutorial") Tutorial tutorial);
}

View File

@@ -0,0 +1,101 @@
package com.corewing.app.modules.admin.biz;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.corewing.app.common.Result;
import com.corewing.app.dto.biz.feedback.FeedbackBatchDeleteRequest;
import com.corewing.app.dto.biz.feedback.FeedbackBatchStatusRequest;
import com.corewing.app.entity.Feedback;
import com.corewing.app.service.FeedbackService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 用户反馈
*/
@Controller
@RequestMapping("/biz/feedback")
public class BizFeedBackController {
@Resource
private FeedbackService feedbackService;
/**
* 反馈管理首页
* @return
*/
@GetMapping("/index")
public String index() {
return "admin/biz/feedback/index";
}
/**
* 查询反馈数据分页
* @param feedback
* @return
*/
@GetMapping("/page")
@ResponseBody
public Result<Page<Feedback>> page(Feedback feedback) {
return Result.success(feedbackService.page(feedback));
}
/**
* 新增反馈
* @param feedback
* @return
*/
@PostMapping("/save")
@ResponseBody
public Result<String> save(@RequestBody Feedback feedback) {
return Result.isBool(feedbackService.save(feedback));
}
/**
* 编辑反馈
* @param feedback
* @return
*/
@PostMapping("/update")
@ResponseBody
public Result<String> update(@RequestBody Feedback feedback) {
return Result.isBool(feedbackService.updateById(feedback));
}
/**
* 删除反馈
* @param id
* @return
*/
@DeleteMapping("/delete")
@ResponseBody
public Result<String> delete(Long id) {
return Result.isBool(feedbackService.removeById(id));
}
/**
* 批量删除反馈
* @param feedbackBatchDeleteRequest
* @return
*/
@PostMapping("/batchDelete")
@ResponseBody
public Result<String> batchDelete(@RequestBody FeedbackBatchDeleteRequest feedbackBatchDeleteRequest) {
return Result.isBool(feedbackService.removeBatchByIds(feedbackBatchDeleteRequest.getIds()));
}
/**
* 批量更改状态
* @param feedbackBatchStatusRequest
* @return
*/
@PostMapping("/batchStatus")
@ResponseBody
public Result<String> batchStatus(@RequestBody FeedbackBatchStatusRequest feedbackBatchStatusRequest) {
return Result.isBool(feedbackService.batchStatus(feedbackBatchStatusRequest));
}
}

View File

@@ -0,0 +1,102 @@
package com.corewing.app.modules.admin.biz;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.corewing.app.common.Result;
import com.corewing.app.dto.biz.firmware.FirmwareBatchDeleteRequest;
import com.corewing.app.entity.Firmware;
import com.corewing.app.service.FirmwareService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
/**
* 固件管理
*/
@Controller
@RequestMapping("/biz/firmware")
public class BizFirmwareController {
@Resource
private FirmwareService firmwareService;
/**
* 固件管理首页
* @return
*/
@GetMapping("/index")
public String index() {
return "admin/biz/firmware/index";
}
/**
* 分页查询
* @param firmware
* @return
*/
@GetMapping("/page")
@ResponseBody
public Result<Page<Firmware>> page(Firmware firmware) {
return Result.success(firmwareService.page(firmware));
}
/**
* 保存
* @param firmware
* @return
*/
@PostMapping("/save")
@ResponseBody
public Result<String> save(@RequestBody Firmware firmware) {
return Result.isBool(firmwareService.save(firmware));
}
/**
* 更新
* @param firmware
* @return
*/
@PostMapping("/update")
@ResponseBody
public Result<String> update(@RequestBody Firmware firmware) {
return Result.isBool(firmwareService.updateById(firmware));
}
/**
* 删除
* @param id
* @return
*/
@DeleteMapping("/delete")
@ResponseBody
public Result<Firmware> delete(Long id) {
return Result.isBool(firmwareService.removeData(id));
}
/**
* 批量删除
* @param firmwareBatchDeleteRequest
* @return
*/
@PostMapping("/batchDelete")
@ResponseBody
public Result<Firmware> batchDelete(@RequestBody FirmwareBatchDeleteRequest firmwareBatchDeleteRequest) {
return Result.isBool(firmwareService.removeBatchByIds(firmwareBatchDeleteRequest.getIds()));
}
/**
* 上传固件
* @param file
* @return
*/
@PostMapping("/uploadFile")
@ResponseBody
public Result<String> uploadFile(MultipartFile file, Long id) {
return Result.isBool(firmwareService.uploadFile(file, id));
}
}

View File

@@ -0,0 +1,61 @@
package com.corewing.app.modules.admin.biz;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.corewing.app.common.Result;
import com.corewing.app.dto.biz.privacyPolicy.PrivacyPolicyBatchDeleteRequest;
import com.corewing.app.entity.PrivacyPolicy;
import com.corewing.app.service.PrivacyPolicyService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* 隐私政策
*/
@Controller
@RequestMapping("/biz/privacy_policy")
public class BizPrivacyPolicyController {
@Resource
private PrivacyPolicyService privacyPolicyService;
@GetMapping("/index")
public String index() {
return "admin/biz/privacyPolicy/index";
}
@GetMapping("/page")
@ResponseBody
public Result<Page<PrivacyPolicy>> page(PrivacyPolicy privacyPolicy) {
return Result.success(privacyPolicyService.page(privacyPolicy));
}
@PostMapping("/save")
@ResponseBody
public Result<String> save(@RequestBody PrivacyPolicy privacyPolicy) {
return Result.isBool(privacyPolicyService.save(privacyPolicy));
}
@PostMapping("/update")
@ResponseBody
public Result<String> update(@RequestBody PrivacyPolicy privacyPolicy) {
return Result.isBool(privacyPolicyService.updateById(privacyPolicy));
}
@DeleteMapping("/delete")
@ResponseBody
public Result<String> delete(Long id) {
return Result.isBool(privacyPolicyService.removeById(id));
}
@PostMapping("/batchDelete")
@ResponseBody
public Result<String> batchDelete(@RequestBody PrivacyPolicyBatchDeleteRequest privacyPolicyBatchDeleteRequest) {
return Result.isBool(privacyPolicyService.removeBatchByIds(privacyPolicyBatchDeleteRequest.getIds()));
}
}

View File

@@ -0,0 +1,143 @@
package com.corewing.app.modules.admin.biz;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.corewing.app.common.Result;
import com.corewing.app.dto.biz.tutorial.CategoryBatchDeleteRequest;
import com.corewing.app.entity.Tutorial;
import com.corewing.app.entity.TutorialCategory;
import com.corewing.app.service.TutorialCategoryService;
import com.corewing.app.service.TutorialService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* 教程指南
*/
@Controller
@RequestMapping("/biz/tutorial")
public class BizTutorialController {
@Resource
private TutorialCategoryService tutorialCategoryService;
@Resource
private TutorialService tutorialService;
/**
* 教程分页
* @param tutorial
* @return
*/
@GetMapping("/page")
@ResponseBody
public Result<Page<Tutorial>> page(Tutorial tutorial) {
return Result.success(tutorialService.page(tutorial));
}
/**
* 新增教程
* @param tutorial
* @return
*/
@PostMapping("/save")
@ResponseBody
public Result<String> save(@RequestBody Tutorial tutorial) {
return Result.isBool(tutorialService.save(tutorial));
}
/**
* 更新教程
* @param tutorial
* @return
*/
@PostMapping("/update")
@ResponseBody
public Result<String> update(@RequestBody Tutorial tutorial) {
return Result.isBool(tutorialService.updateById(tutorial));
}
/**
* 删除教程
* @param id
* @return
*/
@DeleteMapping("/delete")
@ResponseBody
public Result<String> delete(Long id) {
return Result.isBool(tutorialService.remove(id));
}
/**
* 教程管理首页
* @return
*/
@GetMapping("/index")
public String index() {
return "admin/biz/tutorial/index";
}
/**
* 教程分类分页
* @param tutorialCategory
* @return
*/
@GetMapping("/category/page")
@ResponseBody
public Result<Page<TutorialCategory>> categoryPage(TutorialCategory tutorialCategory) {
return Result.success(tutorialCategoryService.page(tutorialCategory));
}
/**
* 新增教程分类
* @param tutorialCategory
* @return
*/
@PostMapping("/category/save")
@ResponseBody
public Result<String> saveCategory(@RequestBody TutorialCategory tutorialCategory) {
tutorialCategory.setType(TutorialCategory.typeCategory);
return Result.isBool(tutorialCategoryService.save(tutorialCategory));
}
/**
* 更新教程分类
* @param tutorialCategory
* @return
*/
@PostMapping("/category/update")
@ResponseBody
public Result<String> updateCategory(@RequestBody TutorialCategory tutorialCategory) {
return Result.isBool(tutorialCategoryService.updateById(tutorialCategory));
}
/**
* 删除教程分类
* @param id
* @return
*/
@DeleteMapping("/category/delete")
@ResponseBody
public Result<String> deleteCategory(Long id) {
return Result.isBool(tutorialCategoryService.removeById(id));
}
/**
* 批量删除教程分类
* @param categoryBatchDeleteRequest
* @return
*/
@PostMapping("/category/batchDelete")
@ResponseBody
public Result<String> batchDeleteCategory(@RequestBody CategoryBatchDeleteRequest categoryBatchDeleteRequest) {
return Result.isBool(tutorialCategoryService.removeBatchByIds(categoryBatchDeleteRequest.getIds()));
}
}

View File

@@ -0,0 +1,106 @@
package com.corewing.app.modules.admin.biz;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.corewing.app.common.Result;
import com.corewing.app.dto.biz.user.BizUserBatchDeleteRequest;
import com.corewing.app.dto.biz.user.BizUserIdRequest;
import com.corewing.app.dto.biz.user.BizUserStatusRequest;
import com.corewing.app.dto.biz.user.ResetPasswordRequest;
import com.corewing.app.entity.User;
import com.corewing.app.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@Controller
@RequestMapping("/biz/user")
public class BizUserController {
@Resource
private UserService userService;
/**
* 跳转到用户管理页面
* @return 页面名称
*/
@GetMapping("/index")
public String index() {
return "admin/biz/user/index";
}
/**
* 获取分页信息
* @param user 用户搜索对象
* @return 用户信息集合
*/
@GetMapping("/page")
@ResponseBody
public Result<Page<User>> page(User user) {
Page<User> sysUserPage = userService.page(user);
return Result.success(sysUserPage);
}
/**
* 修改密码
* @param resetPasswordRequest 修改密码DTO
* @return 成功 or 失败
*/
@PutMapping("/resetPassword")
@ResponseBody
public Result<String> resetPassword(@RequestBody ResetPasswordRequest resetPasswordRequest) {
boolean flag = userService.resetPassword(resetPasswordRequest);
return Result.isBoolAsMsg(flag, "修改密码成功");
}
/**
* 新增用户
* @param user
* @return
*/
@PostMapping("/save")
@ResponseBody
public Result<String> save(@RequestBody User user) {
return Result.isBool(userService.save(user));
}
/**
* 修改用户
* @param user
* @return
*/
@PostMapping("/update")
@ResponseBody
public Result<String> update(@RequestBody User user) {
return Result.isBool(userService.update(user));
}
/**
* 单删用户
* @param bizUserIdRequest
* @return
*/
@PostMapping("/delete")
@ResponseBody
public Result<String> delete(@RequestBody BizUserIdRequest bizUserIdRequest) {
return Result.isBool(userService.removeById(bizUserIdRequest.getId()));
}
/**
* 批量删除用户
* @return
*/
@PostMapping("/batchDelete")
@ResponseBody
public Result<String> batchDelete(@RequestBody BizUserBatchDeleteRequest bizUserBatchDeleteRequest) {
return Result.isBool(userService.removeByIds(bizUserBatchDeleteRequest.getIds()));
}
@PostMapping("/batchStatus")
@ResponseBody
public Result<String> batchStatus(@RequestBody BizUserStatusRequest bizUserStatusRequests) {
return Result.isBool(userService.batchStatus(bizUserStatusRequests));
}
}

View File

@@ -0,0 +1,73 @@
package com.corewing.app.modules.admin.sys;
import com.corewing.app.common.Result;
import com.corewing.app.service.FirmwareService;
import com.corewing.app.service.SysUserService;
import com.corewing.app.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@Controller
public class SysMainController {
@Resource
private UserService userService;
@Resource
private FirmwareService firmwareService;
/**
* 加载页
* @return
*/
@GetMapping({"/", "/index.html"})
public String loading() {
return "admin/loading";
}
/**
* 登录页
* @return
*/
@GetMapping("/admin/login.html")
public String login() {
return "admin/login";
}
/**
* 后台首页
* @return
*/
@GetMapping("/admin/index.html")
public String adminIndex() {
return "admin/main";
}
/**
* 仪表盘
* @return
*/
@GetMapping("/admin/dashboard")
public String dashboard() {
return "admin/dashboard";
}
/**
* 统计
* @return
*/
@GetMapping("/main/statistics")
@ResponseBody
public Result<Map<String, Object>> statistics() {
Map<String, Object> result = new HashMap<>();
result.put("userCount", userService.count ());
result.put("firmwareCount", firmwareService.count());
return Result.success(result);
}
}

View File

@@ -0,0 +1,28 @@
package com.corewing.app.modules.admin.sys;
import com.corewing.app.common.Result;
import com.corewing.app.entity.SysMenu;
import com.corewing.app.service.SysMenuService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import java.util.List;
@Controller
@RequestMapping("/sys/menu")
public class SysMenuController {
@Resource
private SysMenuService sysMenuService;
@GetMapping("/initSysMenu")
@ResponseBody
public Result<List<SysMenu>> initSysMenu() {
List<SysMenu> sysMenus = sysMenuService.initSysMenu();
return Result.success(sysMenus);
}
}

View File

@@ -0,0 +1,19 @@
package com.corewing.app.modules.admin.sys;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 系统设置
*/
@Controller
@RequestMapping("/sys/setting")
public class SysSettingController {
@GetMapping
public String index() {
return "admin/sys/setting";
}
}

View File

@@ -1,12 +1,16 @@
package com.corewing.app.controller;
package com.corewing.app.modules.admin.sys;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.corewing.app.common.Result;
import com.corewing.app.dto.SysLoginRequest;
import com.corewing.app.dto.api.SysLoginRequest;
import com.corewing.app.dto.sys.SysResetPasswordRequest;
import com.corewing.app.dto.sys.SysUserIdRequest;
import com.corewing.app.entity.SysUser;
import com.corewing.app.service.SysUserService;
import com.corewing.app.util.I18nUtil;
import com.corewing.app.util.IpUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
@@ -16,7 +20,7 @@ import java.util.Map;
/**
* 后台管理用户 Controller
*/
@RestController
@Controller
@RequestMapping("/sys/user")
public class SysUserController {
@@ -26,13 +30,49 @@ public class SysUserController {
this.sysUserService = sysUserService;
}
@GetMapping("/index")
public String index() {
return "admin/sys/user/index";
}
@GetMapping("/page")
@ResponseBody
public Result<Page<SysUser>> page(SysUser sysUser) {
Page<SysUser> sysUserPage = sysUserService.page(sysUser);
return Result.success(sysUserPage);
}
@PostMapping("/update")
@ResponseBody
public Result<String> update(@RequestBody SysUser sysUser) {
return Result.isBool(sysUserService.update(sysUser));
}
@PostMapping("/save")
@ResponseBody
public Result<String> save(@RequestBody SysUser sysUser) {
return Result.isBool(sysUserService.save(sysUser));
}
@PutMapping("/resetPassword")
@ResponseBody
public Result<String> resetPassword(@RequestBody SysResetPasswordRequest sysResetPasswordRequest) {
return Result.isBool(sysUserService.resetPassword(sysResetPasswordRequest));
}
@DeleteMapping("/delete")
@ResponseBody
public Result<String> delete(@RequestBody SysUserIdRequest sysUserIdRequest) {
return Result.isBool(sysUserService.removeById(sysUserIdRequest.getId()));
}
/**
* 后台管理登录
*/
@PostMapping("/login")
@ResponseBody
public Result<Map<String, Object>> login(@RequestBody SysLoginRequest request, HttpServletRequest httpRequest) {
try {
// 获取登录IP
String loginIp = IpUtil.getClientIp(httpRequest);
// 执行登录
@@ -57,6 +97,7 @@ public class SysUserController {
* 后台管理登出
*/
@PostMapping("/logout")
@ResponseBody
public Result<String> logout() {
StpUtil.logout();
return Result.success(I18nUtil.getMessage("user.logout.success"));
@@ -66,6 +107,7 @@ public class SysUserController {
* 获取当前登录用户信息
*/
@GetMapping("/info")
@ResponseBody
public Result<SysUser> getUserInfo() {
Long userId = StpUtil.getLoginIdAsLong();
SysUser user = sysUserService.getById(userId);

View File

@@ -0,0 +1,25 @@
package com.corewing.app.modules.app;
import com.corewing.app.common.Result;
import com.corewing.app.entity.ContactMsg;
import com.corewing.app.service.ContactMsgService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/contactMsg")
public class AppContactMsgController {
@Resource
private ContactMsgService contactMsgService;
@PostMapping("/save")
public Result<String> save(@RequestBody ContactMsg contactMsg) {
return Result.isBool(contactMsgService.save(contactMsg));
}
}

View File

@@ -1,10 +1,10 @@
package com.corewing.app.controller;
package com.corewing.app.modules.app;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.corewing.app.common.Result;
import com.corewing.app.dto.FeedbackRequest;
import com.corewing.app.dto.api.FeedbackRequest;
import com.corewing.app.entity.Feedback;
import com.corewing.app.service.FeedbackService;
import com.corewing.app.util.DingTalkUtil;
@@ -23,13 +23,13 @@ import java.util.List;
*/
@RestController
@RequestMapping("/feedback")
public class FeedbackController {
public class AppFeedbackController {
private final FeedbackService feedbackService;
private final DingTalkUtil dingTalkUtil;
private final Ip2RegionUtil ip2RegionUtil;
public FeedbackController(FeedbackService feedbackService, DingTalkUtil dingTalkUtil, Ip2RegionUtil ip2RegionUtil) {
public AppFeedbackController(FeedbackService feedbackService, DingTalkUtil dingTalkUtil, Ip2RegionUtil ip2RegionUtil) {
this.feedbackService = feedbackService;
this.dingTalkUtil = dingTalkUtil;
this.ip2RegionUtil = ip2RegionUtil;

View File

@@ -1,4 +1,4 @@
package com.corewing.app.controller;
package com.corewing.app.modules.app;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -15,11 +15,11 @@ import org.springframework.web.bind.annotation.*;
*/
@RestController
@RequestMapping("/firmware")
public class FirmwareController {
public class AppFirmwareController {
private final FirmwareService firmwareService;
public FirmwareController(FirmwareService firmwareService) {
public AppFirmwareController(FirmwareService firmwareService) {
this.firmwareService = firmwareService;
}

View File

@@ -1,11 +1,11 @@
package com.corewing.app.controller;
package com.corewing.app.modules.app;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.corewing.app.common.Result;
import com.corewing.app.dto.CreateParamRequest;
import com.corewing.app.dto.UpdateParamRequest;
import com.corewing.app.dto.api.CreateParamRequest;
import com.corewing.app.dto.api.UpdateParamRequest;
import com.corewing.app.entity.ParamsCenter;
import com.corewing.app.service.ParamsCenterService;
import com.corewing.app.util.I18nUtil;
@@ -20,11 +20,11 @@ import java.util.List;
*/
@RestController
@RequestMapping("/params")
public class ParamsCenterController {
public class AppParamsCenterController {
private final ParamsCenterService paramsService;
public ParamsCenterController(ParamsCenterService paramsService) {
public AppParamsCenterController(ParamsCenterService paramsService) {
this.paramsService = paramsService;
}

View File

@@ -0,0 +1,53 @@
package com.corewing.app.modules.app;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.corewing.app.common.Result;
import com.corewing.app.entity.PrivacyPolicy;
import com.corewing.app.service.PrivacyPolicyService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import java.util.List;
/**
* 隐私政策与协议
*/
@Controller
@RequestMapping("/privacy_policy")
public class AppPrivacyPolicyController {
@Resource
private PrivacyPolicyService privacyPolicyService;
/**
* 隐私政策列表
* @return
*/
@GetMapping("/view_list/{lang}")
public String viewList(@PathVariable String lang, ModelMap modelMap) {
List<PrivacyPolicy> list = privacyPolicyService.list();
modelMap.put("list", list);
// 获取最新更新时间
modelMap.put("lastUpdateTime", privacyPolicyService.getLastUpdateTime());
return "app/privacyPolicy/index";
}
/**
* 根据类型获取集合数据
* @param category
* @return
*/
@GetMapping("/getListByCategory/{category}")
@ResponseBody
public Result<PrivacyPolicy> getListByCategory(@PathVariable Integer category) {
LambdaQueryWrapper<PrivacyPolicy> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(PrivacyPolicy::getCategory, category);
return Result.success(privacyPolicyService.getOne(wrapper));
}
}

View File

@@ -0,0 +1,49 @@
package com.corewing.app.modules.app;
import cn.dev33.satoken.stp.StpUtil;
import com.corewing.app.common.Result;
import com.corewing.app.dto.api.uploadReplaySessionRequest;
import com.corewing.app.entity.ReplaySession;
import com.corewing.app.service.ReplaySessionService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping("/replay-session")
public class AppReplaySessionController {
@Resource
private ReplaySessionService replaySessionService;
/**
* 获取飞行记录
* @return
*/
@GetMapping("/getReplayList")
public Result<List<ReplaySession>> getReplayList() {
return Result.success(replaySessionService.getReplayList(StpUtil.getLoginId()));
}
/**
* 上传飞行记录
* @param uploadReplaySessionRequests
* @return
*/
@PostMapping("/uploadReplaySession")
public Result<Boolean> uploadReplaySession(@RequestBody List<uploadReplaySessionRequest> uploadReplaySessionRequests) {
return Result.isBool(replaySessionService.uploadReplaySession(uploadReplaySessionRequests));
}
/**
* 根据同步id删除数据
* @param syncId
* @return
*/
@DeleteMapping("/deleteReplaySession/{syncId}")
public Result<Boolean> deleteReplaySession(@PathVariable String syncId) {
return Result.isBool(replaySessionService.deleteReplaySession(syncId));
}
}

View File

@@ -1,19 +1,19 @@
package com.corewing.app.controller;
package com.corewing.app.modules.app;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.corewing.app.common.Result;
import com.corewing.app.dto.api.TutorialListRequest;
import com.corewing.app.entity.Tutorial;
import com.corewing.app.entity.TutorialCategory;
import com.corewing.app.service.TutorialCategoryService;
import com.corewing.app.service.TutorialService;
import com.corewing.app.util.I18nUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -21,24 +21,39 @@ import java.util.List;
* 教程接口
*/
@RequestMapping("/tutorial")
@RestController
@Controller
@Slf4j
public class TutorialController {
public class AppTutorialController {
private final TutorialService tutorialService;
private final TutorialCategoryService tutorialCategoryService;
public TutorialController(TutorialService tutorialService, TutorialCategoryService tutorialCategoryService) {
public AppTutorialController(TutorialService tutorialService, TutorialCategoryService tutorialCategoryService) {
this.tutorialService = tutorialService;
this.tutorialCategoryService = tutorialCategoryService;
}
/**
* 跳转到界面查看教程详情
* @param tutorialId 教程id
* @param model 数据模型
* @return 详情页
*/
@GetMapping("/viewDetail/{tutorialId}")
public String viewDetail(@PathVariable Long tutorialId, ModelMap model) {
Tutorial tutorial = tutorialService.getById(tutorialId);
model.put("tutorial", tutorial);
return "app/tutorial/viewDetail";
}
/**
* 添加查看次数
* @param tutorialId 教程id
* @return
*/
@GetMapping("/addViewCount")
@ResponseBody
public Result<String> addViewCount(@RequestParam Long tutorialId) {
Tutorial tutorial = tutorialService.getById(tutorialId);
if(tutorial == null) {
@@ -56,6 +71,7 @@ public class TutorialController {
*
*/
@GetMapping("/category")
@ResponseBody
public Result<List<TutorialCategory>> category(
@RequestParam(required = false, defaultValue = "0") Integer firstStatus
) {
@@ -77,19 +93,19 @@ public class TutorialController {
*
*/
@GetMapping("/page")
@ResponseBody
public Result<IPage<Tutorial>> getPageList(
@RequestParam(defaultValue = "1") Long current,
@RequestParam(defaultValue = "10") Long size,
@RequestParam(required = false, defaultValue = "0") Integer categoryId,
@RequestParam(required = false, defaultValue = "0") Long categoryId,
@RequestParam(required = false) String tutorialTitle) {
try {
Page<Tutorial> page = new Page<>(current, size);
IPage<Tutorial> pageResult = tutorialService.pageList(page, categoryId, tutorialTitle, I18nUtil.getCurrentLocale().getLanguage());
Page<Tutorial> pageResult = tutorialService.pageList(page, categoryId, tutorialTitle, I18nUtil.getCurrentLocale().getLanguage());
return Result.success(pageResult);
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
}

View File

@@ -1,16 +1,15 @@
package com.corewing.app.controller;
package com.corewing.app.modules.app;
import cn.dev33.satoken.stp.StpUtil;
import com.corewing.app.common.Result;
import com.corewing.app.dto.LoginRequest;
import com.corewing.app.dto.RegisterRequest;
import com.corewing.app.dto.SendCodeRequest;
import com.corewing.app.dto.UpdatePasswordRequest;
import com.corewing.app.dto.api.*;
import com.corewing.app.dto.biz.user.ResetPasswordRequest;
import com.corewing.app.entity.User;
import com.corewing.app.service.UserService;
import com.corewing.app.service.VerifyCodeService;
import com.corewing.app.util.I18nUtil;
import com.corewing.app.util.IpUtil;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
@@ -22,12 +21,12 @@ import java.util.Map;
*/
@RestController
@RequestMapping("/user")
public class UserController {
public class AppUserController {
private final UserService userService;
private final VerifyCodeService verifyCodeService;
public UserController(UserService userService, VerifyCodeService verifyCodeService) {
public AppUserController(UserService userService, VerifyCodeService verifyCodeService) {
this.userService = userService;
this.verifyCodeService = verifyCodeService;
}
@@ -132,11 +131,12 @@ public class UserController {
/**
* 更新用户信息
*/
@PutMapping
@PutMapping("/update")
public Result<String> update(@RequestBody User user) {
Long userId = StpUtil.getLoginIdAsLong();
user.setId(userId);
// 不允许通过此接口修改密码
user.setPassword(null);
boolean success = userService.updateById(user);
if (success) {
return Result.success(I18nUtil.getMessage("user.update.success"));
@@ -173,4 +173,14 @@ public class UserController {
return Result.error(e.getMessage());
}
}
/**
* 忘记密码
* @param request
* @return
*/
@PutMapping("/forgetPassword")
public Result<String> forgetPassword(@RequestBody ForgetPasswordRequest request) {
return Result.isBool(userService.forgetPassword(request));
}
}

View File

@@ -0,0 +1,18 @@
package com.corewing.app.modules.directive;
import com.corewing.app.service.SysOptionService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service("option")
public class SysOptionDirective {
@Resource
private SysOptionService sysOptionService;
public String getValue(String key) {
return sysOptionService.getValueByKey(key);
}
}

View File

@@ -0,0 +1,7 @@
package com.corewing.app.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.corewing.app.entity.ContactMsg;
public interface ContactMsgService extends IService<ContactMsg> {
}

View File

@@ -3,6 +3,7 @@ package com.corewing.app.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.corewing.app.dto.biz.feedback.FeedbackBatchStatusRequest;
import com.corewing.app.entity.Feedback;
import java.util.List;
@@ -12,6 +13,9 @@ import java.util.List;
*/
public interface FeedbackService extends IService<Feedback> {
Page<Feedback> page(Feedback feedback);
/**
* 创建反馈
*
@@ -47,4 +51,11 @@ public interface FeedbackService extends IService<Feedback> {
* @return 是否成功
*/
boolean updateStatus(Long id, Integer status);
/**
* 批量修改状态
* @param feedbackBatchStatusRequest
* @return
*/
boolean batchStatus(FeedbackBatchStatusRequest feedbackBatchStatusRequest);
}

View File

@@ -1,13 +1,23 @@
package com.corewing.app.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.corewing.app.common.Result;
import com.corewing.app.entity.Firmware;
import org.springframework.web.multipart.MultipartFile;
/**
* 固件 Service 接口
*/
public interface FirmwareService extends IService<Firmware> {
/**
* 查询固件分页数据
* @param firmware
* @return
*/
Page<Firmware> page(Firmware firmware);
/**
* 根据固件名称查询固件
*
@@ -15,4 +25,19 @@ public interface FirmwareService extends IService<Firmware> {
* @return 固件信息
*/
Firmware getByFirmwareName(String firmwareName);
/**
* 上传文件
* @param file
* @param id
* @return
*/
boolean uploadFile(MultipartFile file, Long id);
/**
* 根据id删除固件
* @param id
* @return
*/
boolean removeData(Long id);
}

View File

@@ -0,0 +1,12 @@
package com.corewing.app.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.corewing.app.entity.PrivacyPolicy;
public interface PrivacyPolicyService extends IService<PrivacyPolicy> {
Page<PrivacyPolicy> page(PrivacyPolicy privacyPolicy);
String getLastUpdateTime();
}

View File

@@ -0,0 +1,17 @@
package com.corewing.app.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.corewing.app.dto.api.uploadReplaySessionRequest;
import com.corewing.app.entity.ReplaySession;
import java.util.List;
public interface ReplaySessionService extends IService<ReplaySession> {
List<ReplaySession> getReplayList(Object loginId);
boolean uploadReplaySession(List<uploadReplaySessionRequest> uploadReplaySessionRequests);
boolean deleteReplaySession(String syncId);
}

View File

@@ -0,0 +1,10 @@
package com.corewing.app.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.corewing.app.entity.SysMenu;
import java.util.List;
public interface SysMenuService extends IService<SysMenu> {
List<SysMenu> initSysMenu();
}

View File

@@ -0,0 +1,10 @@
package com.corewing.app.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.corewing.app.entity.SysOption;
public interface SysOptionService extends IService<SysOption> {
String getValueByKey(String key);
}

View File

@@ -1,6 +1,8 @@
package com.corewing.app.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.corewing.app.dto.sys.SysResetPasswordRequest;
import com.corewing.app.entity.SysUser;
/**
@@ -8,6 +10,12 @@ import com.corewing.app.entity.SysUser;
*/
public interface SysUserService extends IService<SysUser> {
Page<SysUser> page(SysUser sysUser);
boolean save(SysUser sysUser);
boolean update(SysUser sysUser);
/**
* 根据用户名查询用户
*
@@ -33,4 +41,11 @@ public interface SysUserService extends IService<SysUser> {
* @param loginIp 登录IP
*/
void updateLoginInfo(Long userId, String loginIp);
/**
* 修改密码
* @param sysResetPasswordRequest
* @return
*/
boolean resetPassword(SysResetPasswordRequest sysResetPasswordRequest);
}

View File

@@ -1,7 +1,11 @@
package com.corewing.app.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.corewing.app.entity.TutorialCategory;
public interface TutorialCategoryService extends IService<TutorialCategory> {
Page<TutorialCategory> page(TutorialCategory tutorialCategory);
}

View File

@@ -3,8 +3,21 @@ package com.corewing.app.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.corewing.app.dto.api.TutorialListRequest;
import com.corewing.app.entity.Tutorial;
import com.corewing.app.entity.TutorialCategory;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
public interface TutorialService extends IService<Tutorial> {
IPage<Tutorial> pageList(Page<Tutorial> page, int categoryId, String tutorialTitle, String lang);
Page<Tutorial> pageList(Page<Tutorial> page, Long categoryId, String tutorialTitle, String lang);
Page<Tutorial> page(Tutorial tutorial);
boolean save(Tutorial tutorial);
boolean update(Tutorial tutorial);
boolean remove(Long id);
}

View File

@@ -1,13 +1,39 @@
package com.corewing.app.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.corewing.app.dto.api.ForgetPasswordRequest;
import com.corewing.app.dto.biz.user.BizUserStatusRequest;
import com.corewing.app.dto.biz.user.ResetPasswordRequest;
import com.corewing.app.entity.User;
/**
* 应用用户 Service 接口
*/
public interface UserService extends IService<User> {
/**
* 获取用户分页数据
* @param user
* @return
*/
Page<User> page(User user);
/**
* 新增用户数据
* @param user
* @return
*/
boolean save(User user);
/**
* 更新用户数据
* @param user
* @return
*/
boolean update(User user);
/**
* 根据用户名查询用户
*
@@ -74,4 +100,25 @@ public interface UserService extends IService<User> {
* @param loginIp 登录IP
*/
void updateLoginInfo(Long userId, String loginIp);
/**
* 修改密码
* @param resetPasswordRequest
* @return
*/
boolean resetPassword(ResetPasswordRequest resetPasswordRequest);
/**
* 批量修改状态
* @param bizUserStatusRequest
* @return
*/
boolean batchStatus(BizUserStatusRequest bizUserStatusRequest);
/**
* 忘记密码
* @param request
* @return
*/
boolean forgetPassword(ForgetPasswordRequest request);
}

View File

@@ -0,0 +1,12 @@
package com.corewing.app.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.corewing.app.entity.ContactMsg;
import com.corewing.app.mapper.ContactMsgMapper;
import com.corewing.app.service.ContactMsgService;
import org.springframework.stereotype.Service;
@Service
public class ContactMsgServiceImpl extends ServiceImpl<ContactMsgMapper, ContactMsg> implements ContactMsgService {
}

View File

@@ -1,24 +1,44 @@
package com.corewing.app.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.corewing.app.common.page.PageContext;
import com.corewing.app.dto.biz.feedback.FeedbackBatchStatusRequest;
import com.corewing.app.entity.Feedback;
import com.corewing.app.mapper.FeedbackMapper;
import com.corewing.app.service.FeedbackService;
import com.corewing.app.util.I18nUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.List;
import static io.lettuce.core.GeoArgs.Unit.m;
/**
* 问题反馈 Service 实现类
*/
@Service
public class FeedbackServiceImpl extends ServiceImpl<FeedbackMapper, Feedback> implements FeedbackService {
@Resource
private FeedbackMapper feedbackMapper;
@Override
public Page<Feedback> page(Feedback feedback) {
Page<Feedback> page = PageContext.getPage(Feedback.class);
// LambdaQueryWrapper<Feedback> wrapper = new LambdaQueryWrapper<>();
// wrapper.eq(feedback.getStatus() != null, Feedback::getStatus, feedback.getStatus());
// wrapper.like(StringUtils.hasText(feedback.getTitle()), Feedback::getTitle, feedback.getTitle());
// wrapper.eq(StringUtils.hasText(feedback.getFeedbackType()), Feedback::getFeedbackType, feedback.getFeedbackType());
return feedbackMapper.page(page, feedback);
}
@Override
public boolean createFeedback(Feedback feedback) {
// 设置默认状态为待处理
@@ -75,4 +95,16 @@ public class FeedbackServiceImpl extends ServiceImpl<FeedbackMapper, Feedback> i
return this.updateById(feedback);
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean batchStatus(FeedbackBatchStatusRequest feedbackBatchStatusRequest) {
feedbackBatchStatusRequest.getIds().forEach(id -> {
LambdaUpdateWrapper<Feedback> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(Feedback::getId, id);
wrapper.set(Feedback::getStatus, feedbackBatchStatusRequest.getStatus());
update(wrapper);
});
return true;
}
}

View File

@@ -1,11 +1,19 @@
package com.corewing.app.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.corewing.app.common.page.PageContext;
import com.corewing.app.entity.Firmware;
import com.corewing.app.mapper.FirmwareMapper;
import com.corewing.app.service.FirmwareService;
import com.corewing.app.util.OSSUploadUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
/**
* 固件 Service 实现类
@@ -13,10 +21,91 @@ import org.springframework.stereotype.Service;
@Service
public class FirmwareServiceImpl extends ServiceImpl<FirmwareMapper, Firmware> implements FirmwareService {
@Override
public Page<Firmware> page(Firmware firmware) {
Page<Firmware> page = PageContext.getPage(Firmware.class);
LambdaQueryWrapper<Firmware> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.hasText(firmware.getFirmwareName()), Firmware::getFirmwareName, firmware.getFirmwareName());
queryWrapper.eq(firmware.getFirmwareType() != null, Firmware::getFirmwareType, firmware.getFirmwareType());
return page(page, queryWrapper);
}
@Override
public Firmware getByFirmwareName(String firmwareName) {
LambdaQueryWrapper<Firmware> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Firmware::getFirmwareName, firmwareName);
return this.getOne(wrapper);
}
@Override
public boolean uploadFile(MultipartFile file, Long id) {
try {
String downloadUrl = OSSUploadUtil.uploadFile(file.getInputStream(), file.getOriginalFilename());
Firmware firmware = getById(id);
firmware.setFirmwareSize(file.getSize());
firmware.setDownloadUrl(downloadUrl);
return updateById(firmware);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean removeData(Long id) {
Firmware firmware = getById(id);
if(firmware != null ){
if(firmware.getDownloadUrl() != null) {
OSSUploadUtil.deleteFile(getUrlLastSegment(firmware.getDownloadUrl()));
}
return removeById(id);
}
return false;
}
/**
* 获取 URL 路径的最后一节(忽略查询参数 ? 和锚点 #
* @param urlStr URL字符串
* @return 最后一节如文件名、资源ID异常/无路径时返回空字符串
*/
public static String getUrlLastSegment(String urlStr) {
// 1. 空值判断
if (urlStr == null || urlStr.trim().isEmpty()) {
return "";
}
// 2. 移除查询参数(? 及后面的内容)
int queryIndex = urlStr.indexOf('?');
if (queryIndex != -1) {
urlStr = urlStr.substring(0, queryIndex);
}
// 3. 移除锚点(# 及后面的内容)
int anchorIndex = urlStr.indexOf('#');
if (anchorIndex != -1) {
urlStr = urlStr.substring(0, anchorIndex);
}
// 4. 找到最后一个 "/" 的位置
int lastSlashIndex = urlStr.lastIndexOf('/');
// 5. 处理特殊情况:无 "/" 或 "/" 在末尾
if (lastSlashIndex == -1) {
// 无路径(如 "https://example.com" 或 "example.com"
return "";
}
if (lastSlashIndex == urlStr.length() - 1) {
// 路径以 "/" 结尾(如 "https://example.com/folder/"),需找倒数第二个 "/"
urlStr = urlStr.substring(0, lastSlashIndex);
lastSlashIndex = urlStr.lastIndexOf('/');
// 若倒数第二个 "/" 不存在(如 "https://example.com/"),返回空
if (lastSlashIndex == -1) {
return "";
}
}
// 6. 截取最后一个 "/" 后面的内容
return urlStr.substring(lastSlashIndex + 1);
}
}

View File

@@ -0,0 +1,33 @@
package com.corewing.app.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.corewing.app.common.page.PageContext;
import com.corewing.app.entity.PrivacyPolicy;
import com.corewing.app.mapper.PrivacyPolicyMapper;
import com.corewing.app.service.PrivacyPolicyService;
import com.corewing.app.util.DateUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@Service
public class PrivacyPolicyServiceImpl extends ServiceImpl<PrivacyPolicyMapper, PrivacyPolicy> implements PrivacyPolicyService {
@Override
public Page<PrivacyPolicy> page(PrivacyPolicy privacyPolicy) {
Page<PrivacyPolicy> page = PageContext.getPage(PrivacyPolicy.class);
LambdaQueryWrapper<PrivacyPolicy> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.hasText(privacyPolicy.getTitle()), PrivacyPolicy::getTitle, privacyPolicy.getTitle());
queryWrapper.eq(privacyPolicy.getVisible() != null, PrivacyPolicy::getVisible, privacyPolicy.getVisible());
return page(page, queryWrapper);
}
@Override
public String getLastUpdateTime() {
LambdaQueryWrapper<PrivacyPolicy> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.orderByDesc(PrivacyPolicy::getUpdateTime, PrivacyPolicy::getCreateTime);
PrivacyPolicy privacyPolicy = list(queryWrapper).get(0);
return DateUtils.format(privacyPolicy.getUpdateTime() == null ? privacyPolicy.getCreateTime() : privacyPolicy.getUpdateTime(), "yyyy年MM月dd日");
}
}

View File

@@ -0,0 +1,121 @@
package com.corewing.app.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.corewing.app.dto.api.TrajectoryData;
import com.corewing.app.dto.api.uploadReplaySessionRequest;
import com.corewing.app.entity.ReplaySession;
import com.corewing.app.mapper.ReplaySessionMapper;
import com.corewing.app.service.ReplaySessionService;
import com.corewing.app.util.OSSUploadUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.List;
@Service
public class ReplaySessionServiceImpl extends ServiceImpl<ReplaySessionMapper, ReplaySession> implements ReplaySessionService {
@Resource
private Gson gson;
/**
* 获取飞行记录
* @param loginId
* @return
*/
@Override
public List<ReplaySession> getReplayList(Object loginId) {
LambdaQueryWrapper<ReplaySession> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ReplaySession::getUserId, loginId);
// 去oss里面下载对应的json文件流解析到文件里面
List<ReplaySession> list = list(queryWrapper);
list.forEach(m -> {
try {
InputStream inputStream = OSSUploadUtil.downloadFileToStream(m.getOssPath());
List<TrajectoryData> dataList = new Gson().fromJson(
new InputStreamReader(inputStream, StandardCharsets.UTF_8),
new TypeToken<List<TrajectoryData>>() {}.getType() // 增加 .getType()
);
m.setTrajectoryDataList(dataList);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
return list;
}
/**
* 同步飞行记录
* @param uploadReplaySessionRequests
* @return
*/
@Transactional
@Override
public boolean uploadReplaySession(List<uploadReplaySessionRequest> uploadReplaySessionRequests) {
uploadReplaySessionRequests.forEach(replaySessionRequest -> {
// 校验是否重复
LambdaQueryWrapper<ReplaySession> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ReplaySession::getSyncId, replaySessionRequest.getSync_id());
if (count(queryWrapper) > 0) {
return;
}
ReplaySession replaySession = new ReplaySession();
replaySession.setSyncId(replaySessionRequest.getSync_id());
replaySession.setName(replaySessionRequest.getName());
replaySession.setDuration(replaySessionRequest.getDuration());
replaySession.setStartTime(replaySessionRequest.getStart_time());
replaySession.setEndTime(replaySessionRequest.getEnd_time());
replaySession.setDescription(replaySessionRequest.getDescription());
replaySession.setDataCount(replaySessionRequest.getData_count());
replaySession.setCreatedAt(replaySessionRequest.getCreated_at());
replaySession.setUpdatedAt(replaySessionRequest.getUpdated_at());
replaySession.setUserId(Long.valueOf(StpUtil.getLoginId().toString()));
String json = gson.toJson(replaySessionRequest.getTrajectoryDataList());
// 必须指定编码,避免平台默认编码问题
InputStream inputStream = new ByteArrayInputStream(
json.getBytes(StandardCharsets.UTF_8)
);
String ossPath = "trajectory/" + replaySessionRequest.getSync_id() + ".json";
OSSUploadUtil.uploadFile(inputStream, ossPath);
replaySession.setOssPath(ossPath);
save(replaySession);
});
return true;
}
/**
* 根据syncId删除数据
* @param syncId
* @return
*/
@Override
public boolean deleteReplaySession(String syncId) {
LambdaQueryWrapper<ReplaySession> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ReplaySession::getSyncId, syncId);
ReplaySession replaySession = this.getOne(queryWrapper);
if (replaySession == null) {
return false;
}
OSSUploadUtil.deleteFile("trajectory/" + syncId + ".json");
return removeById(replaySession.getId());
}
}

View File

@@ -0,0 +1,23 @@
package com.corewing.app.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.corewing.app.entity.SysMenu;
import com.corewing.app.mapper.SysMenuMapper;
import com.corewing.app.service.SysMenuService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {
@Override
public List<SysMenu> initSysMenu() {
LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysMenu::isVisible, Boolean.TRUE);
queryWrapper.orderByAsc(SysMenu::getSort);
return list(queryWrapper);
}
}

View File

@@ -0,0 +1,19 @@
package com.corewing.app.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.corewing.app.entity.SysOption;
import com.corewing.app.mapper.SysOptionMapper;
import com.corewing.app.service.SysOptionService;
import org.springframework.stereotype.Service;
@Service
public class SysOptionServiceImpl extends ServiceImpl<SysOptionMapper, SysOption> implements SysOptionService {
@Override
public String getValueByKey(String key) {
LambdaQueryWrapper<SysOption> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysOption::getKey, key);
return getOne(queryWrapper).getValue();
}
}

View File

@@ -2,7 +2,10 @@ package com.corewing.app.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.corewing.app.common.page.PageContext;
import com.corewing.app.dto.sys.SysResetPasswordRequest;
import com.corewing.app.entity.SysUser;
import com.corewing.app.mapper.SysUserMapper;
import com.corewing.app.service.SysUserService;
@@ -20,6 +23,24 @@ import java.time.LocalDateTime;
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
public Page<SysUser> page(SysUser sysUser) {
Page<SysUser> page = PageContext.getPage(SysUser.class);
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(sysUser.getStatus() != null, SysUser::getStatus, sysUser.getStatus());
queryWrapper.like(StringUtils.hasText(sysUser.getUsername()), SysUser::getUsername, sysUser.getUsername());
return page(page, queryWrapper);
}
public boolean save(SysUser sysUser) {
String encryptPassword = DigestUtils.md5DigestAsHex(sysUser.getPassword().getBytes(StandardCharsets.UTF_8));
sysUser.setPassword(encryptPassword);
return super.save(sysUser);
}
public boolean update(SysUser sysUser) {
return updateById(sysUser);
}
@Override
public SysUser getByUsername(String username) {
if (!StringUtils.hasText(username)) {
@@ -66,4 +87,16 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
user.setLoginTime(LocalDateTime.now());
this.updateById(user);
}
@Override
public boolean resetPassword(SysResetPasswordRequest sysResetPasswordRequest) {
SysUser sysUser = getById(sysResetPasswordRequest.getUserId());
if(sysUser == null) {
throw new RuntimeException(I18nUtil.getMessage("error.user.not.found"));
}
String encryptPassword = DigestUtils.md5DigestAsHex(sysResetPasswordRequest.getPassword().getBytes(StandardCharsets.UTF_8));
sysUser.setPassword(encryptPassword);
return updateById(sysUser);
}
}

View File

@@ -1,11 +1,24 @@
package com.corewing.app.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.corewing.app.common.page.PageContext;
import com.corewing.app.entity.TutorialCategory;
import com.corewing.app.mapper.TutorialCategoryMapper;
import com.corewing.app.service.TutorialCategoryService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@Service
public class TutorialCategoryServiceImpl extends ServiceImpl<TutorialCategoryMapper, TutorialCategory> implements TutorialCategoryService {
public Page<TutorialCategory> page(TutorialCategory tutorialCategory){
Page<TutorialCategory> page = PageContext.getPage(TutorialCategory.class);
LambdaQueryWrapper<TutorialCategory> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.hasText(tutorialCategory.getCategoryTitle()), TutorialCategory::getCategoryTitle, tutorialCategory.getCategoryTitle());
return page(page, queryWrapper);
}
}

View File

@@ -1,24 +1,75 @@
package com.corewing.app.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.corewing.app.common.page.PageContext;
import com.corewing.app.dto.api.TutorialListRequest;
import com.corewing.app.entity.Tutorial;
import com.corewing.app.entity.TutorialCategory;
import com.corewing.app.entity.TutorialCategoryRelation;
import com.corewing.app.mapper.TutorialMapper;
import com.corewing.app.service.TutorialCategoryRelationService;
import com.corewing.app.service.TutorialCategoryService;
import com.corewing.app.service.TutorialService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.List;
@Service
public class TutorialServiceImpl extends ServiceImpl<TutorialMapper, Tutorial> implements TutorialService {
private final TutorialMapper tutorialMapper;
@Resource
private TutorialMapper tutorialMapper;
public TutorialServiceImpl(TutorialMapper tutorialMapper) {
this.tutorialMapper = tutorialMapper;
@Resource
private TutorialCategoryRelationService tutorialCategoryRelationService;
@Resource
private TutorialCategoryService tutorialCategoryService;
@Override
public Page<Tutorial> pageList(Page<Tutorial> page, Long categoryId, String tutorialTitle, String lang) {
return tutorialMapper.pageList(page, categoryId, tutorialTitle, lang);
}
@Override
public IPage<Tutorial> pageList(Page<Tutorial> page, int categoryId, String tutorialTitle, String lang) {
return tutorialMapper.pageList(page, categoryId, tutorialTitle, lang);
public Page<Tutorial> page(Tutorial tutorial) {
Page<Tutorial> page = PageContext.getPage(Tutorial.class);
return tutorialMapper.page(page, tutorial);
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean save(Tutorial tutorial) {
super.save(tutorial);
TutorialCategoryRelation tutorialCategoryRelation = new TutorialCategoryRelation();
tutorialCategoryRelation.setTutorialId(tutorial.getId());
tutorialCategoryRelation.setCategoryId(tutorial.getCategoryId());
return tutorialCategoryRelationService.save(tutorialCategoryRelation);
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean update(Tutorial tutorial) {
super.updateById(tutorial);
tutorialCategoryRelationService.remove(new LambdaQueryWrapper<TutorialCategoryRelation>().eq(TutorialCategoryRelation::getCategoryId, tutorial.getCategoryId()));
TutorialCategoryRelation tutorialCategoryRelation = new TutorialCategoryRelation();
tutorialCategoryRelation.setTutorialId(tutorial.getId());
tutorialCategoryRelation.setCategoryId(tutorial.getCategoryId());
return tutorialCategoryRelationService.save(tutorialCategoryRelation);
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean remove(Long id) {
Tutorial tutorial = getById(id);
removeById(id);
return tutorialCategoryRelationService.remove(new LambdaQueryWrapper<TutorialCategoryRelation>().eq(TutorialCategoryRelation::getCategoryId, tutorial.getCategoryId()));
}
}

View File

@@ -2,14 +2,22 @@ package com.corewing.app.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.corewing.app.common.page.PageContext;
import com.corewing.app.dto.api.ForgetPasswordRequest;
import com.corewing.app.dto.biz.user.BizUserStatusRequest;
import com.corewing.app.dto.biz.user.ResetPasswordRequest;
import com.corewing.app.entity.User;
import com.corewing.app.mapper.UserMapper;
import com.corewing.app.service.UserService;
import com.corewing.app.service.VerifyCodeService;
import com.corewing.app.util.I18nUtil;
import com.corewing.app.util.Ip2RegionUtil;
import com.corewing.app.util.RedisUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
@@ -23,10 +31,66 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
private final VerifyCodeService verifyCodeService;
private final Ip2RegionUtil ip2RegionUtil;
private final UserMapper userMapper;
private final RedisUtil redisUtil;
public UserServiceImpl(VerifyCodeService verifyCodeService, Ip2RegionUtil ip2RegionUtil) {
public UserServiceImpl(VerifyCodeService verifyCodeService, Ip2RegionUtil ip2RegionUtil, UserMapper userMapper, RedisUtil redisUtil) {
this.verifyCodeService = verifyCodeService;
this.ip2RegionUtil = ip2RegionUtil;
this.userMapper = userMapper;
this.redisUtil = redisUtil;
}
@Override
public Page<User> page(User user) {
Page<User> page = PageContext.getPage(User.class);
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.hasText(user.getNickName()), User::getNickName, user.getNickName());
queryWrapper.like(StringUtils.hasText(user.getUsername()), User::getUsername, user.getUsername());
queryWrapper.like(user.getStatus() != null, User::getStatus, user.getStatus());
return page(page, queryWrapper);
}
@Override
public boolean save(User user) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername, user.getUsername());
if(count(queryWrapper) > 0) {
throw new RuntimeException(I18nUtil.getMessage(I18nUtil.getMessage("error.username.exists")));
}
return super.save(user);
}
@Override
public boolean update(User user) {
// 校验用户名
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername, user.getUsername());
queryWrapper.ne(User::getId, user.getId());
if(count(queryWrapper) > 0) {
throw new RuntimeException(I18nUtil.getMessage(I18nUtil.getMessage("error.username.exists")));
}
// 校验手机号码
LambdaQueryWrapper<User> checkPhoneWrapper = new LambdaQueryWrapper<>();
checkPhoneWrapper.eq(User::getUsername, user.getUsername());
checkPhoneWrapper.ne(User::getId, user.getId());
if(count(checkPhoneWrapper) > 0) {
throw new RuntimeException(I18nUtil.getMessage(I18nUtil.getMessage("error.phone.exists")));
}
// 校验邮箱
LambdaQueryWrapper<User> checkEmailWrapper = new LambdaQueryWrapper<>();
checkEmailWrapper.eq(User::getUsername, user.getUsername());
checkEmailWrapper.ne(User::getId, user.getId());
if(count(checkEmailWrapper) > 0) {
throw new RuntimeException(I18nUtil.getMessage(I18nUtil.getMessage("error.email.exists")));
}
return updateById(user);
}
@Override
@@ -81,6 +145,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
// 验证密码MD5加密
String encryptPassword = DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));
// 客户端已经使用加密
if (!encryptPassword.equals(user.getPassword())) {
throw new RuntimeException(I18nUtil.getMessage("error.password.incorrect"));
}
@@ -166,4 +231,46 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
user.setLoginRegion(ip2RegionUtil.getRegion(loginIp));
this.updateById(user);
}
@Override
public boolean resetPassword(ResetPasswordRequest resetPasswordRequest) {
User user = getById(resetPasswordRequest.getUserId());
if(user == null) {
throw new RuntimeException(I18nUtil.getMessage("error.user.not.found"));
}
// 更新新密码
String newPasswordMd5 = DigestUtils.md5DigestAsHex(resetPasswordRequest.getPassword().getBytes(StandardCharsets.UTF_8));
user.setPassword(newPasswordMd5);
return updateById(user);
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean batchStatus(BizUserStatusRequest bizUserStatusRequest) {
bizUserStatusRequest.getIds().forEach(id -> {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getId, id);
wrapper.set(User::getStatus, bizUserStatusRequest.getStatus());
this.update(wrapper);
});
return true;
}
@Override
public boolean forgetPassword(ForgetPasswordRequest request) {
User user = getByAccount(request.getAccount());
if(user == null) {
throw new RuntimeException(I18nUtil.getMessage("error.user.not.found"));
}
String codeKey = String.format("verify_code:%s:%s", "forget", request.getAccount());
String checkCode = redisUtil.get(codeKey).toString();
if(!checkCode.equalsIgnoreCase(request.getVerificationCode())) {
throw new RuntimeException(I18nUtil.getMessage("error.verify.code.invalid"));
}
String encryptPassword = DigestUtils.md5DigestAsHex(request.getPassword().getBytes(StandardCharsets.UTF_8));
user.setPassword(encryptPassword);
return updateById(user);
}
}

View File

@@ -1,16 +1,12 @@
package com.corewing.app.service.impl;
import com.corewing.app.service.VerifyCodeService;
import com.corewing.app.util.EmailUtil;
import com.corewing.app.util.I18nUtil;
import com.corewing.app.util.RedisUtil;
import com.corewing.app.util.SmsBaoUtil;
import com.corewing.app.util.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Random;
import java.util.regex.Pattern;
/**
* 验证码服务实现类
@@ -33,16 +29,6 @@ public class VerifyCodeServiceImpl implements VerifyCodeService {
*/
private static final int CODE_LENGTH = 6;
/**
* 手机号正则
*/
private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
/**
* 邮箱正则
*/
private static final Pattern EMAIL_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$");
public VerifyCodeServiceImpl(RedisUtil redisUtil, SmsBaoUtil smsBaoUtil, EmailUtil emailUtil) {
this.redisUtil = redisUtil;
this.smsBaoUtil = smsBaoUtil;
@@ -59,11 +45,7 @@ public class VerifyCodeServiceImpl implements VerifyCodeService {
throw new RuntimeException(I18nUtil.getMessage("error.verify.type.empty"));
}
// 判断是手机号还是邮箱
boolean isPhone = PHONE_PATTERN.matcher(account).matches();
boolean isEmail = EMAIL_PATTERN.matcher(account).matches();
if (!isPhone && !isEmail) {
if (!AccountUtil.isPhone(account) && !AccountUtil.isEmail(account)) {
throw new RuntimeException(I18nUtil.getMessage("error.account.format.invalid"));
}
@@ -77,7 +59,7 @@ public class VerifyCodeServiceImpl implements VerifyCodeService {
log.info("验证码已生成: account={}, type={}, code={}", account, type, code);
// 发送验证码
if (isPhone) {
if (AccountUtil.isPhone(account)) {
// 发送短信验证码
boolean success = smsBaoUtil.sendVerifyCode(account, code);
if (!success) {

View File

@@ -0,0 +1,35 @@
package com.corewing.app.util;
import java.util.regex.Pattern;
public class AccountUtil {
/**
* 手机号正则
*/
private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
/**
* 邮箱正则
*/
private static final Pattern EMAIL_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$");
/**
* 校验是否是手机号
* @param email
* @return
*/
public static boolean isEmail(String email) {
return EMAIL_PATTERN.matcher(email).matches();
}
/**
* 校验是否是手机
* @param phoneNumber
* @return
*/
public static boolean isPhone(String phoneNumber) {
return PHONE_PATTERN.matcher(phoneNumber).matches();
}
}

View File

@@ -0,0 +1,14 @@
package com.corewing.app.util;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DateUtils {
// 格式化LocalDateTime为字符串
public static String format(LocalDateTime time, String pattern) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
return time.format(formatter);
}
}

View File

@@ -0,0 +1,236 @@
package com.corewing.app.util;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.*;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component
public class OSSUploadUtil {
/**
* 用户登录名称 oss@1563058769875871.onaliyun.com
* AccessKey ID LTAI5tKUhXrGxZ5Gj3exWpkG
* AccessKey Secret PaBTMp3BhcOUgLQhJWmOkhfJhTlzhV
*/
private static final String ENDPOINT = "oss-cn-shenzhen.aliyuncs.com"; // 地域节点
private static final String ACCESS_KEY_ID = "LTAI5tKUhXrGxZ5Gj3exWpkG"; // 替换为你的 AccessKey ID
private static final String ACCESS_KEY_SECRET = "PaBTMp3BhcOUgLQhJWmOkhfJhTlzhV"; // 替换为你的 AccessKey Secret
private static final String BUCKET_NAME = "corewing-app"; // 替换为你的 Bucket 名称
/**
* 上传文件到 OSS
* @param inputStream 文件输入流
* @param objectName OSS 中存储的文件路径(如 "firmware/20231104/update.bin"
* @return 上传成功后的文件 URL
*/
public static String uploadFile(InputStream inputStream, String objectName) {
// 创建 OSS 客户端实例
OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
try {
// 设置文件元数据(可选,如 Content-Type
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(getContentType(objectName)); // 自动识别文件类型
// 上传文件
PutObjectRequest request = new PutObjectRequest(BUCKET_NAME, objectName, inputStream, metadata);
PutObjectResult result = ossClient.putObject(request);
ossClient.setObjectAcl(
BUCKET_NAME, // Bucket 名称
objectName, // 刚上传的文件路径
CannedAccessControlList.PublicRead // 公共可读权限
);
// 生成文件访问 URL公网访问需 Bucket 设为公共读)
return String.format("https://%s.%s/%s", BUCKET_NAME, ENDPOINT, objectName);
} finally {
// 关闭 OSS 客户端,释放资源
if (ossClient != null) {
ossClient.shutdown();
}
}
}
/**
* 上传本地文件到 OSS
* @param localFilePath 本地文件路径(如 "D:/firmware/update.bin"
* @param objectName OSS 中存储的文件路径
* @return 上传成功后的文件 URL
*/
public static String uploadLocalFile(String localFilePath, String objectName) {
OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
try {
File file = new File(localFilePath);
ossClient.putObject(BUCKET_NAME, objectName, file);
ossClient.setObjectAcl(
BUCKET_NAME, // Bucket 名称
objectName, // 刚上传的文件路径
CannedAccessControlList.PublicRead // 公共可读权限
);
return String.format("https://%s.%s/%s", BUCKET_NAME, ENDPOINT, objectName);
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
}
/**
* 从 OSS 下载文件到本地路径
* @param objectName OSS 中存储的文件路径(如 "firmware/20231104/update.bin"
* @param localFilePath 本地保存路径(如 "D:/downloads/update.bin"
* @return 下载是否成功
*/
public static boolean downloadFileToLocal(String objectName, String localFilePath) {
OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
try {
// 检查文件是否存在
if (!ossClient.doesObjectExist(BUCKET_NAME, objectName)) {
throw new FileNotFoundException("OSS 文件不存在:" + objectName);
}
// 下载文件到本地
ossClient.getObject(new GetObjectRequest(BUCKET_NAME, objectName), new File(localFilePath));
return true;
} catch (OSSException | FileNotFoundException e) {
return false;
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
}
/**
* 从 OSS 获取文件输入流(需手动关闭流)
* @param objectName OSS 中存储的文件路径
* @return 文件输入流(使用后需调用 close() 关闭)
* @throws IOException 当文件不存在或下载失败时抛出
*/
public static InputStream downloadFileToStream(String objectName) throws IOException {
OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
try {
// 检查文件是否存在
if (!ossClient.doesObjectExist(BUCKET_NAME, objectName)) {
throw new FileNotFoundException("OSS 文件不存在:" + objectName);
}
// 获取 OSS 文件对象并返回输入流
OSSObject ossObject = ossClient.getObject(BUCKET_NAME, objectName);
return ossObject.getObjectContent();
} catch (OSSException e) {
throw new IOException("OSS 服务异常:" + e.getMessage(), e);
} finally {
// 注意:此处不能关闭 ossClient否则输入流会被提前关闭
// 需在调用方使用完输入流后,手动关闭 ossClient 和输入流
// (或通过 try-with-resources 自动关闭)
}
}
/**
* 从 OSS 删除单个文件
* @param objectName OSS 中存储的文件路径(如 "firmware/20231104/update.bin"
* @return 删除是否成功(文件不存在时返回 false
*/
public static boolean deleteFile(String objectName) {
OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
try {
// 先检查文件是否存在
if (!ossClient.doesObjectExist(BUCKET_NAME, objectName)) {
System.out.println("OSS 文件不存在,删除失败:" + objectName);
return false;
}
// 执行删除操作
ossClient.deleteObject(BUCKET_NAME, objectName);
System.out.println("OSS 文件删除成功:" + objectName);
return true;
} catch (OSSException e) {
System.err.println("OSS 服务异常,删除失败:" + e.getMessage());
return false;
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
}
/**
* 批量删除 OSS 文件3.17.4 版本终极兼容方案)
* 逻辑:先执行批量删除,再逐个校验文件是否存在,确认删除结果
* @param objectNames OSS 文件路径列表(如 ["firmware/update1.bin", "firmware/update2.bin"]
* @return 批量删除结果key文件路径value是否删除成功
*/
public static Map<String, Boolean> batchDeleteFiles(List<String> objectNames) {
Map<String, Boolean> deleteResult = new HashMap<>();
if (objectNames == null || objectNames.isEmpty()) {
System.out.println("批量删除失败:文件列表为空");
return deleteResult;
}
OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
try {
// 第一步:执行批量删除(忽略 SDK 返回结果,只保证执行删除操作)
DeleteObjectsRequest request = new DeleteObjectsRequest(BUCKET_NAME);
request.setKeys(objectNames); // 直接传入文件路径列表
ossClient.deleteObjects(request); // 执行删除,不依赖返回结果
// 第二步:逐个校验文件是否存在,确认删除结果
for (String objectName : objectNames) {
boolean isDeleted = !ossClient.doesObjectExist(BUCKET_NAME, objectName);
deleteResult.put(objectName, isDeleted);
if (isDeleted) {
System.out.println("删除成功:" + objectName);
} else {
System.err.println("删除失败:文件仍存在 -> " + objectName);
}
}
} catch (OSSException e) {
System.err.printf("OSS 服务异常:错误码=%s原因=%s%n",
e.getErrorCode(), e.getErrorMessage());
// 服务异常时,所有文件标记为删除失败
objectNames.forEach(name -> deleteResult.put(name, false));
} catch (Exception e) {
System.err.println("批量删除未知异常:" + e.getMessage());
objectNames.forEach(name -> deleteResult.put(name, false));
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
return deleteResult;
}
/**
* 根据文件名获取 Content-Type
* @param fileName 文件名(如 "update.bin"
* @return Content-Type
*/
private static String getContentType(String fileName) {
String suffix = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
switch (suffix) {
case "bin": return "application/octet-stream";
case "jpg": case "jpeg": return "image/jpeg";
case "png": return "image/png";
case "txt": return "text/plain";
// 其他文件类型可自行扩展
default: return "application/octet-stream";
}
}
}

View File

@@ -55,4 +55,8 @@ public class TutorialVO {
*/
private String categoryTitle;
private String lang;
private Long categoryId;
}

View File

@@ -1,6 +1,11 @@
# 应用服务 WEB 访问端口
server.port=8080
# 单文件最大大小例如10MB支持单位B, KB, MB, GB
spring.servlet.multipart.max-file-size=10MB
# 一次请求中所有文件的总大小例如50MB
spring.servlet.multipart.max-request-size=50MB
# 数据源配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
@@ -45,6 +50,11 @@ spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-wait=-1ms
# MongoDB 配置
spring.data.mongodb.host=120.24.204.180
spring.data.mongodb.database=app
spring.data.mongodb.port=27017
# Sa-Token 配置
sa-token.token-name=Authorization
sa-token.timeout=2592000

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.corewing.app.mapper.FeedbackMapper">
<!-- 结果映射 -->
<resultMap id="VOResultMap" type="com.corewing.app.entity.Feedback">
<id column="id" property="id"/>
<result column="feedback_type" property="feedbackType"/>
<result column="title" property="title"/>
<result column="content" property="content"/>
<result column="contact" property="contact"/>
<result column="status" property="status"/>
<result column="nick_name" property="nickName"/>
<result column="username" property="username"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<!-- 基础查询SQL片段 -->
<sql id="selectVOSql">
select f.*,u.nick_name, u.username
from app_feedback f
left join app_user u on f.user_id = u.id
</sql>
<!-- 分页查询 -->
<select id="page" resultMap="VOResultMap">
<include refid="selectVOSql"/>
<where>
<if test="feedback.status != null">
AND f.status = #{feedback.status}
</if>
<if test="feedback.title != null and feedback.title != ''">
AND f.title like CONCAT('%', #{feedback.title}, '%')
</if>
<if test="feedback.feedbackType != null and feedback.feedbackType != ''">
AND f.feedbackType like CONCAT('%', #{feedback.feedbackType}, '%')
</if>
</where>
</select>
</mapper>

View File

@@ -11,14 +11,16 @@
<result column="view_count" property="viewCount"/>
<result column="recommend_status" property="recommendStatus"/>
<result column="status" property="status"/>
<result column="lang" property="lang"/>
<result column="category_title" property="categoryTitle"/>
<result column="category_id" property="categoryId"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<!-- 基础查询SQL片段 -->
<sql id="selectVOSql">
select c.*, cc.category_title
select c.*, cc.category_title, cc.id as category_id
from app_tutorial c
left join app_tutorial_category_relation ccr on c.id = ccr.tutorial_id
left join app_tutorial_category cc on cc.id = ccr.category_id
@@ -36,9 +38,28 @@
<if test="tutorialTitle != null and tutorialTitle != ''">
AND c.tutorial_title like CONCAT('%', #{tutorialTitle}, '%')
</if>
</where>
ORDER BY c.recommend_status,c.create_time asc
</select>
<!-- 分页查询 -->
<select id="page" resultMap="VOResultMap">
<include refid="selectVOSql"/>
<where>
<if test="tutorial.categoryId != null and tutorial.categoryId != 0">
AND cc.id = #{tutorial.categoryId}
</if>
<if test="tutorial.tutorialTitle != null and tutorial.tutorialTitle != ''">
AND c.tutorial_title like CONCAT('%', #{tutorial.tutorialTitle}, '%')
</if>
<if test="tutorial.lang != null and tutorial.lang != ''">
AND c.lang = #{tutorial.lang}
</if>
<if test="tutorial.status != null">
AND c.status = #{tutorial.status}
</if>
</where>
</select>
</mapper>

View File

@@ -1,494 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>后台管理系统</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
:root {
--primary-color: #5B5FDE;
--secondary-color: #0EA5E9;
--accent-color: #FF6B6B;
--error-color: #F43F5E;
--success-color: #10B981;
--warning-color: #F59E0B;
--info-color: #3B82F6;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #F3F4F6;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
/* 侧边栏样式 */
.sidebar {
min-height: 100vh;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
color: white;
padding: 0;
position: fixed;
left: 0;
top: 0;
width: 180px;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
}
.sidebar-header {
padding: 18px 12px;
text-align: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.sidebar-header h4 {
font-size: 16px;
font-weight: 700;
margin: 0;
}
.sidebar-header i {
margin-right: 5px;
font-size: 16px;
}
.sidebar-menu {
padding: 12px 8px;
}
.sidebar .nav-link {
color: rgba(255, 255, 255, 0.85);
padding: 9px 10px;
border-radius: 8px;
margin: 3px 0;
transition: all 0.3s;
font-weight: 500;
font-size: 13px;
display: flex;
align-items: center;
}
.sidebar .nav-link i {
width: 18px;
font-size: 15px;
}
.sidebar .nav-link:hover {
background-color: rgba(255, 255, 255, 0.15);
color: white;
transform: translateX(5px);
}
.sidebar .nav-link.active {
background-color: rgba(255, 255, 255, 0.25);
color: white;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.sidebar-footer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 12px 8px;
}
.btn-logout {
width: 100%;
padding: 9px;
background: rgba(255, 255, 255, 0.15);
border: 1px solid rgba(255, 255, 255, 0.3);
color: white;
border-radius: 8px;
font-weight: 600;
font-size: 13px;
transition: all 0.3s;
}
.btn-logout:hover {
background: rgba(255, 255, 255, 0.25);
transform: translateY(-2px);
}
/* 顶部导航栏 */
.navbar {
background-color: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
padding: 12px 25px;
}
.navbar-brand {
font-size: 18px;
font-weight: 700;
color: #1F2937;
}
.user-info {
display: flex;
align-items: center;
gap: 8px;
color: #4B5563;
font-weight: 500;
font-size: 14px;
}
.user-info i {
font-size: 20px;
color: var(--primary-color);
}
/* 主内容区 */
.main-content {
margin-left: 180px;
}
/* 内容区域 */
.content-wrapper {
padding: 25px;
}
/* 欢迎卡片 */
.welcome-card {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
color: white;
border-radius: 16px;
padding: 35px;
margin-bottom: 25px;
box-shadow: 0 10px 30px rgba(91, 95, 222, 0.2);
position: relative;
overflow: hidden;
}
.welcome-card::before {
content: '';
position: absolute;
width: 250px;
height: 250px;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
top: -80px;
right: -80px;
}
.welcome-card h2 {
font-size: 26px;
font-weight: 700;
margin-bottom: 10px;
position: relative;
z-index: 1;
}
.welcome-card p {
font-size: 15px;
opacity: 0.95;
position: relative;
z-index: 1;
}
/* 统计卡片 */
.stat-card {
background: white;
border-radius: 12px;
padding: 25px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
transition: all 0.3s;
border: 1px solid #E5E7EB;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
.stat-card i {
font-size: 40px;
opacity: 0.9;
margin-bottom: 12px;
}
.stat-card h3 {
font-size: 30px;
font-weight: 700;
margin: 12px 0 6px;
color: #1F2937;
}
.stat-card p {
color: #6B7280;
font-size: 13px;
margin: 0;
}
.stat-card.primary i { color: var(--primary-color); }
.stat-card.success i { color: var(--success-color); }
.stat-card.warning i { color: var(--warning-color); }
.stat-card.info i { color: var(--info-color); }
/* 页面标题 */
.page-title {
font-size: 24px;
font-weight: 700;
color: #1F2937;
margin-bottom: 20px;
}
/* 卡片容器 */
.card {
background: white;
border-radius: 12px;
border: 1px solid #E5E7EB;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.card-body {
padding: 25px;
}
/* 响应式 */
@media (max-width: 768px) {
.sidebar {
width: 100%;
position: relative;
min-height: auto;
}
.main-content {
margin-left: 0;
}
.sidebar-footer {
position: static;
margin-top: 15px;
}
.welcome-card {
padding: 25px;
}
.welcome-card h2 {
font-size: 20px;
}
.content-wrapper {
padding: 20px;
}
}
</style>
</head>
<body>
<div id="app">
<!-- 侧边栏 -->
<nav class="sidebar">
<div class="sidebar-header">
<h4>
<i class="bi bi-shield-lock-fill"></i>
CoreWing
</h4>
</div>
<div class="sidebar-menu">
<ul class="nav flex-column">
<li class="nav-item">
<a :class="['nav-link', currentPage === 'dashboard' ? 'active' : '']"
href="#"
@click.prevent="currentPage = 'dashboard'">
<i class="bi bi-speedometer2"></i>
仪表盘
</a>
</li>
<li class="nav-item">
<a :class="['nav-link', currentPage === 'users' ? 'active' : '']"
href="#"
@click.prevent="currentPage = 'users'">
<i class="bi bi-people-fill"></i>
用户管理
</a>
</li>
<li class="nav-item">
<a :class="['nav-link', currentPage === 'settings' ? 'active' : '']"
href="#"
@click.prevent="currentPage = 'settings'">
<i class="bi bi-gear-fill"></i>
系统设置
</a>
</li>
</ul>
</div>
<div class="sidebar-footer">
<button class="btn-logout" @click="handleLogout">
<i class="bi bi-box-arrow-right me-2"></i>
退出登录
</button>
</div>
</nav>
<!-- 主内容区 -->
<main class="main-content">
<!-- 顶部导航栏 -->
<nav class="navbar navbar-expand-lg navbar-light">
<div class="container-fluid">
<span class="navbar-brand">欢迎回来</span>
<div class="user-info">
<i class="bi bi-person-circle"></i>
<span>{{ userInfo.realName || userInfo.username }}</span>
</div>
</div>
</nav>
<!-- 内容区域 -->
<div class="content-wrapper">
<!-- 仪表盘 -->
<div v-if="currentPage === 'dashboard'">
<div class="welcome-card">
<h2>
<i class="bi bi-emoji-smile"></i>
你好,{{ userInfo.realName || userInfo.username }}
</h2>
<p class="mb-0 mt-2">欢迎使用 CoreWing 后台管理系统</p>
</div>
<div class="row">
<div class="col-md-4 mb-4">
<div class="stat-card primary">
<i class="bi bi-people-fill"></i>
<h3>1,234</h3>
<p>总用户数</p>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="stat-card success">
<i class="bi bi-file-earmark-text-fill"></i>
<h3>567</h3>
<p>文档数量</p>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="stat-card warning">
<i class="bi bi-graph-up"></i>
<h3>89%</h3>
<p>系统负载</p>
</div>
</div>
</div>
</div>
<!-- 用户管理 -->
<div v-if="currentPage === 'users'">
<h3 class="page-title">用户管理</h3>
<div class="card">
<div class="card-body">
<p>用户管理功能开发中...</p>
</div>
</div>
</div>
<!-- 系统设置 -->
<div v-if="currentPage === 'settings'">
<h3 class="page-title">系统设置</h3>
<div class="card">
<div class="card-body">
<p>系统设置功能开发中...</p>
</div>
</div>
</div>
</div>
</main>
</div>
<!-- Vue 3 -->
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>
<!-- Axios -->
<script src="https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js"></script>
<!-- Bootstrap 5 JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
userInfo: {
username: '',
realName: '',
userId: ''
},
currentPage: 'dashboard'
}
},
methods: {
async loadUserInfo() {
// 从 localStorage 获取用户信息
this.userInfo.username = localStorage.getItem('username') || '';
this.userInfo.realName = localStorage.getItem('realName') || '';
this.userInfo.userId = localStorage.getItem('userId') || '';
// 也可以从服务器获取最新的用户信息
const token = localStorage.getItem('token');
if (token) {
try {
axios.defaults.headers.common['satoken'] = token;
const response = await axios.get('/sys/user/info');
if (response.data.code === 200) {
const user = response.data.data;
this.userInfo.username = user.username;
this.userInfo.realName = user.realName || user.username;
this.userInfo.userId = user.id;
}
} catch (error) {
console.error('获取用户信息失败:', error);
}
}
},
async handleLogout() {
if (confirm('确定要退出登录吗?')) {
try {
const token = localStorage.getItem('token');
if (token) {
axios.defaults.headers.common['satoken'] = token;
await axios.post('/sys/user/logout');
}
} catch (error) {
console.error('退出登录失败:', error);
} finally {
// 清除本地存储
localStorage.clear();
// 跳转到登录页
window.location.href = '/admin/login.html';
}
}
},
checkLogin() {
const token = localStorage.getItem('token');
if (!token) {
// 未登录,跳转到登录页
window.location.href = '/admin/login.html';
}
}
},
mounted() {
// 检查登录状态
this.checkLogin();
// 加载用户信息
this.loadUserInfo();
}
}).mount('#app');
</script>
</body>
</html>

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