Compare commits
14 Commits
feature-ad
...
feature-cc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8c9e694ba | ||
|
|
c4c8ccff4e | ||
|
|
ab63329f2f | ||
|
|
1028c0773f | ||
|
|
08c6481c51 | ||
|
|
2224b43fcb | ||
|
|
9c1b701594 | ||
|
|
b77952164b | ||
|
|
5210ea9554 | ||
| 6b7f6947db | |||
| 1f81b6fbc1 | |||
| d53bc3966c | |||
| 88abc41a9e | |||
| e4f1385411 |
482
GIT_CONVENTION.md
Normal file
482
GIT_CONVENTION.md
Normal file
@@ -0,0 +1,482 @@
|
|||||||
|
# Git 使用规范
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
- [分支命名规范](#分支命名规范)
|
||||||
|
- [提交信息规范](#提交信息规范)
|
||||||
|
- [工作流程](#工作流程)
|
||||||
|
- [代码审查](#代码审查)
|
||||||
|
- [最佳实践](#最佳实践)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 分支命名规范
|
||||||
|
|
||||||
|
### 分支类型
|
||||||
|
|
||||||
|
#### 1. 主分支
|
||||||
|
- **master/main**: 生产环境分支,只接受合并,不允许直接提交
|
||||||
|
- **develop/dev**: 开发主分支,用于集成所有功能
|
||||||
|
|
||||||
|
#### 2. 功能分支 (Feature)
|
||||||
|
```
|
||||||
|
feature/功能名称
|
||||||
|
feature/功能名称-简短描述
|
||||||
|
```
|
||||||
|
**示例:**
|
||||||
|
- `feature/user-login`
|
||||||
|
- `feature/payment-integration`
|
||||||
|
- `feature/contacts-friends`
|
||||||
|
|
||||||
|
#### 3. 修复分支 (Bugfix)
|
||||||
|
```
|
||||||
|
bugfix/问题描述
|
||||||
|
fix/问题描述
|
||||||
|
```
|
||||||
|
**示例:**
|
||||||
|
- `bugfix/login-error`
|
||||||
|
- `fix/memory-leak`
|
||||||
|
|
||||||
|
#### 4. 热修复分支 (Hotfix)
|
||||||
|
```
|
||||||
|
hotfix/问题描述
|
||||||
|
```
|
||||||
|
**示例:**
|
||||||
|
- `hotfix/critical-security-patch`
|
||||||
|
- `hotfix/payment-bug`
|
||||||
|
|
||||||
|
#### 5. 发布分支 (Release)
|
||||||
|
```
|
||||||
|
release/版本号
|
||||||
|
```
|
||||||
|
**示例:**
|
||||||
|
- `release/v1.0.0`
|
||||||
|
- `release/v2.1.0`
|
||||||
|
|
||||||
|
### 命名规则
|
||||||
|
- 使用小写字母
|
||||||
|
- 多个单词用连字符 `-` 分隔
|
||||||
|
- 避免使用下划线 `_` 或空格
|
||||||
|
- 分支名要有意义,能清楚表达分支用途
|
||||||
|
- 避免使用特殊字符:`~`, `^`, `:`, `?`, `*`, `[`, `\`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 提交信息规范
|
||||||
|
|
||||||
|
### 提交信息格式
|
||||||
|
```
|
||||||
|
<type>(<scope>): <subject>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type 类型
|
||||||
|
|
||||||
|
| 类型 | 说明 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| `feat` | 新功能 | `feat: 添加用户登录功能` |
|
||||||
|
| `fix` | 修复bug | `fix: 修复登录验证失败问题` |
|
||||||
|
| `docs` | 文档更新 | `docs: 更新API文档` |
|
||||||
|
| `style` | 代码格式调整(不影响功能) | `style: 格式化代码` |
|
||||||
|
| `refactor` | 代码重构 | `refactor: 重构用户服务类` |
|
||||||
|
| `perf` | 性能优化 | `perf: 优化数据库查询性能` |
|
||||||
|
| `test` | 测试相关 | `test: 添加用户登录单元测试` |
|
||||||
|
| `chore` | 构建/工具/依赖更新 | `chore: 更新依赖包版本` |
|
||||||
|
| `ci` | CI/CD相关 | `ci: 配置GitHub Actions` |
|
||||||
|
| `build` | 构建系统相关 | `build: 更新Maven配置` |
|
||||||
|
|
||||||
|
### Scope 范围(可选)
|
||||||
|
- 指定修改的模块或文件
|
||||||
|
- 示例:`feat(user): 添加用户注册功能`
|
||||||
|
|
||||||
|
### Subject 主题
|
||||||
|
- 简短描述,不超过50个字符
|
||||||
|
- 使用中文或英文,保持项目统一
|
||||||
|
- 首字母小写,结尾不加句号
|
||||||
|
- 使用祈使语气(如:添加、修复、更新)
|
||||||
|
|
||||||
|
### Body 正文(可选)
|
||||||
|
- 详细描述修改内容
|
||||||
|
- 说明为什么修改,如何修改
|
||||||
|
- 每行不超过72个字符
|
||||||
|
- 用空行与 subject 分隔
|
||||||
|
|
||||||
|
### Footer 页脚(可选)
|
||||||
|
- 关联Issue:`Closes #123`
|
||||||
|
- 破坏性变更:`BREAKING CHANGE: 修改了API接口`
|
||||||
|
|
||||||
|
### 提交信息示例
|
||||||
|
|
||||||
|
#### 简单提交
|
||||||
|
```
|
||||||
|
feat: 添加联系人管理功能
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 带scope的提交
|
||||||
|
```
|
||||||
|
feat(user): 添加用户头像上传功能
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 详细提交
|
||||||
|
```
|
||||||
|
feat(contact): 添加联系人管理功能
|
||||||
|
|
||||||
|
- 实现联系人列表查询
|
||||||
|
- 添加联系人添加/删除接口
|
||||||
|
- 集成Redis缓存优化性能
|
||||||
|
|
||||||
|
Closes #45
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 修复提交
|
||||||
|
```
|
||||||
|
fix: 修复登录验证失败问题
|
||||||
|
|
||||||
|
修复了当用户密码包含特殊字符时验证失败的问题
|
||||||
|
|
||||||
|
Fixes #67
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 工作流程
|
||||||
|
|
||||||
|
### 1. 创建功能分支
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 从develop分支创建新分支
|
||||||
|
git checkout develop
|
||||||
|
git pull origin develop
|
||||||
|
git checkout -b feature/your-feature-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 开发过程
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 经常提交代码
|
||||||
|
git add .
|
||||||
|
git commit -m "feat: 实现XXX功能"
|
||||||
|
|
||||||
|
# 定期同步主分支
|
||||||
|
git checkout develop
|
||||||
|
git pull origin develop
|
||||||
|
git checkout feature/your-feature-name
|
||||||
|
git merge develop
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 提交前检查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查代码状态
|
||||||
|
git status
|
||||||
|
|
||||||
|
# 查看修改内容
|
||||||
|
git diff
|
||||||
|
|
||||||
|
# 查看提交历史
|
||||||
|
git log --oneline
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 推送分支
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 首次推送
|
||||||
|
git push -u origin feature/your-feature-name
|
||||||
|
|
||||||
|
# 后续推送
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 合并到主分支
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 方式1: 通过Pull Request(推荐)
|
||||||
|
# 在Git平台创建PR,代码审查后合并
|
||||||
|
|
||||||
|
# 方式2: 本地合并(不推荐,除非紧急情况)
|
||||||
|
git checkout develop
|
||||||
|
git pull origin develop
|
||||||
|
git merge feature/your-feature-name
|
||||||
|
git push origin develop
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 清理分支
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 删除本地分支
|
||||||
|
git branch -d feature/your-feature-name
|
||||||
|
|
||||||
|
# 删除远程分支
|
||||||
|
git push origin --delete feature/your-feature-name
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 代码审查
|
||||||
|
|
||||||
|
### Pull Request 规范
|
||||||
|
|
||||||
|
#### PR标题格式
|
||||||
|
```
|
||||||
|
[类型] 简短描述
|
||||||
|
```
|
||||||
|
**示例:**
|
||||||
|
- `[Feature] 添加联系人管理功能`
|
||||||
|
- `[Fix] 修复登录验证问题`
|
||||||
|
- `[Refactor] 重构用户服务类`
|
||||||
|
|
||||||
|
#### PR描述模板
|
||||||
|
```markdown
|
||||||
|
## 变更说明
|
||||||
|
简要描述本次PR的主要变更内容
|
||||||
|
|
||||||
|
## 变更类型
|
||||||
|
- [ ] 新功能
|
||||||
|
- [ ] Bug修复
|
||||||
|
- [ ] 代码重构
|
||||||
|
- [ ] 文档更新
|
||||||
|
- [ ] 性能优化
|
||||||
|
- [ ] 其他
|
||||||
|
|
||||||
|
## 测试说明
|
||||||
|
描述如何测试这些变更
|
||||||
|
|
||||||
|
## 相关Issue
|
||||||
|
关联的Issue编号: #123
|
||||||
|
|
||||||
|
## 截图(如适用)
|
||||||
|
[添加相关截图]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 审查检查清单
|
||||||
|
|
||||||
|
#### 提交者
|
||||||
|
- [ ] 代码符合项目规范
|
||||||
|
- [ ] 已添加必要的测试
|
||||||
|
- [ ] 已更新相关文档
|
||||||
|
- [ ] 提交信息清晰明确
|
||||||
|
- [ ] 无编译错误和警告
|
||||||
|
- [ ] 已进行自测
|
||||||
|
|
||||||
|
#### 审查者
|
||||||
|
- [ ] 代码逻辑正确
|
||||||
|
- [ ] 代码风格一致
|
||||||
|
- [ ] 无明显的性能问题
|
||||||
|
- [ ] 无安全隐患
|
||||||
|
- [ ] 测试覆盖充分
|
||||||
|
- [ ] 文档更新完整
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 提交频率
|
||||||
|
- ✅ **推荐**: 频繁提交,每次提交完成一个小功能
|
||||||
|
- ❌ **不推荐**: 大量代码一次性提交
|
||||||
|
|
||||||
|
### 2. 提交粒度
|
||||||
|
- ✅ **推荐**: 每次提交只做一件事
|
||||||
|
- ❌ **不推荐**: 一个提交包含多个不相关的修改
|
||||||
|
|
||||||
|
### 3. 提交前检查
|
||||||
|
```bash
|
||||||
|
# 检查代码格式
|
||||||
|
# 运行测试
|
||||||
|
# 检查编译错误
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 避免的操作
|
||||||
|
- ❌ 不要在master/main分支直接提交
|
||||||
|
- ❌ 不要提交临时文件、日志文件
|
||||||
|
- ❌ 不要提交敏感信息(密码、密钥等)
|
||||||
|
- ❌ 不要强制推送主分支 (`git push --force`)
|
||||||
|
|
||||||
|
### 5. 使用 .gitignore
|
||||||
|
确保 `.gitignore` 文件包含:
|
||||||
|
```
|
||||||
|
# 编译文件
|
||||||
|
*.class
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
|
||||||
|
# IDE文件
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
# 日志文件
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# 依赖目录
|
||||||
|
node_modules/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# 配置文件(包含敏感信息)
|
||||||
|
application-local.yml
|
||||||
|
config.properties
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 冲突处理
|
||||||
|
```bash
|
||||||
|
# 拉取最新代码
|
||||||
|
git pull origin develop
|
||||||
|
|
||||||
|
# 如果有冲突,解决冲突后
|
||||||
|
git add .
|
||||||
|
git commit -m "fix: 解决合并冲突"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. 撤销操作
|
||||||
|
|
||||||
|
#### 撤销工作区修改
|
||||||
|
```bash
|
||||||
|
git checkout -- <file>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 撤销暂存区修改
|
||||||
|
```bash
|
||||||
|
git reset HEAD <file>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 修改最后一次提交
|
||||||
|
```bash
|
||||||
|
git commit --amend -m "新的提交信息"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 回退到指定提交
|
||||||
|
```bash
|
||||||
|
# 软回退(保留修改)
|
||||||
|
git reset --soft <commit-hash>
|
||||||
|
|
||||||
|
# 硬回退(丢弃修改)
|
||||||
|
git reset --hard <commit-hash>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 常用命令速查
|
||||||
|
|
||||||
|
### 分支操作
|
||||||
|
```bash
|
||||||
|
# 查看所有分支
|
||||||
|
git branch -a
|
||||||
|
|
||||||
|
# 创建分支
|
||||||
|
git branch <branch-name>
|
||||||
|
|
||||||
|
# 切换分支
|
||||||
|
git checkout <branch-name>
|
||||||
|
|
||||||
|
# 创建并切换分支
|
||||||
|
git checkout -b <branch-name>
|
||||||
|
|
||||||
|
# 删除本地分支
|
||||||
|
git branch -d <branch-name>
|
||||||
|
|
||||||
|
# 删除远程分支
|
||||||
|
git push origin --delete <branch-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 提交操作
|
||||||
|
```bash
|
||||||
|
# 查看状态
|
||||||
|
git status
|
||||||
|
|
||||||
|
# 添加文件
|
||||||
|
git add <file>
|
||||||
|
git add .
|
||||||
|
|
||||||
|
# 提交
|
||||||
|
git commit -m "提交信息"
|
||||||
|
|
||||||
|
# 查看提交历史
|
||||||
|
git log
|
||||||
|
git log --oneline
|
||||||
|
git log --graph --oneline --all
|
||||||
|
```
|
||||||
|
|
||||||
|
### 远程操作
|
||||||
|
```bash
|
||||||
|
# 查看远程仓库
|
||||||
|
git remote -v
|
||||||
|
|
||||||
|
# 拉取代码
|
||||||
|
git pull origin <branch-name>
|
||||||
|
|
||||||
|
# 推送代码
|
||||||
|
git push origin <branch-name>
|
||||||
|
|
||||||
|
# 获取远程更新(不合并)
|
||||||
|
git fetch origin
|
||||||
|
```
|
||||||
|
|
||||||
|
### 其他实用命令
|
||||||
|
```bash
|
||||||
|
# 查看差异
|
||||||
|
git diff
|
||||||
|
git diff <branch1> <branch2>
|
||||||
|
|
||||||
|
# 暂存修改
|
||||||
|
git stash
|
||||||
|
git stash pop
|
||||||
|
|
||||||
|
# 查看文件历史
|
||||||
|
git log -- <file>
|
||||||
|
|
||||||
|
# 查看某行的修改历史
|
||||||
|
git blame <file>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 版本标签规范
|
||||||
|
|
||||||
|
### 标签命名
|
||||||
|
使用语义化版本号:`v主版本号.次版本号.修订号`
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
- `v1.0.0` - 初始发布
|
||||||
|
- `v1.1.0` - 新功能
|
||||||
|
- `v1.1.1` - Bug修复
|
||||||
|
- `v2.0.0` - 重大更新
|
||||||
|
|
||||||
|
### 标签操作
|
||||||
|
```bash
|
||||||
|
# 创建标签
|
||||||
|
git tag -a v1.0.0 -m "版本1.0.0发布"
|
||||||
|
|
||||||
|
# 推送标签
|
||||||
|
git push origin v1.0.0
|
||||||
|
|
||||||
|
# 推送所有标签
|
||||||
|
git push origin --tags
|
||||||
|
|
||||||
|
# 查看标签
|
||||||
|
git tag
|
||||||
|
git show v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **保护主分支**: master/main 分支应该设置保护规则,禁止直接推送
|
||||||
|
2. **定期同步**: 开发过程中定期从主分支同步代码,避免冲突积累
|
||||||
|
3. **及时清理**: 合并后的分支及时删除,保持仓库整洁
|
||||||
|
4. **提交前测试**: 确保代码可以正常编译和运行
|
||||||
|
5. **代码审查**: 重要功能必须经过代码审查才能合并
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 参考资源
|
||||||
|
|
||||||
|
- [Git官方文档](https://git-scm.com/doc)
|
||||||
|
- [Conventional Commits](https://www.conventionalcommits.org/)
|
||||||
|
- [Semantic Versioning](https://semver.org/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**最后更新**: 2024年
|
||||||
|
|
||||||
41
pom.xml
41
pom.xml
@@ -7,41 +7,49 @@
|
|||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
<name>dating</name>
|
<name>dating</name>
|
||||||
<description>dating</description>
|
<description>dating</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
<spring-boot.version>2.6.13</spring-boot.version>
|
<spring-boot.version>2.6.13</spring-boot.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
<!-- 核心依赖:Spring Boot基础、数据库访问、Web功能等 -->
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- Spring Boot 核心启动器 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter</artifactId>
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MyBatis 持久层框架 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mybatis</groupId>
|
<groupId>org.mybatis</groupId>
|
||||||
<artifactId>mybatis</artifactId>
|
<artifactId>mybatis</artifactId>
|
||||||
<version>3.5.10</version>
|
<version>3.5.10</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Redis 缓存支持 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot Web 开发支持 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MySQL 数据库连接器 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.mysql</groupId>
|
<groupId>com.mysql</groupId>
|
||||||
<artifactId>mysql-connector-j</artifactId>
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Lombok 工具,用于简化实体类开发 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
@@ -49,26 +57,28 @@
|
|||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Added MyBatis Spring Boot Starter dependency -->
|
<!-- MyBatis Spring Boot 启动器 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mybatis.spring.boot</groupId>
|
<groupId>org.mybatis.spring.boot</groupId>
|
||||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||||
<version>2.2.0</version>
|
<version>2.2.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot 测试支持 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Mockito 测试工具 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mockito</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
<artifactId>mockito-inline</artifactId>
|
<artifactId>mockito-inline</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- JUnit Platform Launcher for resolving junit-platform-launcher:1.8.2 issue -->
|
<!-- JUnit 5 测试平台启动器 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.platform</groupId>
|
<groupId>org.junit.platform</groupId>
|
||||||
<artifactId>junit-platform-launcher</artifactId>
|
<artifactId>junit-platform-launcher</artifactId>
|
||||||
@@ -76,71 +86,79 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Apache Commons Lang3 工具包 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
<version>3.12.0</version>
|
<version>3.12.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- OkHttp(用于调用API) -->
|
<!-- OkHttp HTTP 客户端 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.squareup.okhttp3</groupId>
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
<artifactId>okhttp</artifactId>
|
<artifactId>okhttp</artifactId>
|
||||||
<version>4.12.0</version>
|
<version>4.12.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- AOP起步依赖 -->
|
<!-- AOP(面向切面编程)起步依赖 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-aop</artifactId>
|
<artifactId>spring-boot-starter-aop</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- WebSocket 起步依赖 -->
|
<!-- WebSocket 实时通信起步依赖 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 阿里云相关依赖 -->
|
<!-- 阿里云服务相关依赖 -->
|
||||||
|
<!-- 阿里云对象存储服务(OSS) SDK -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun.oss</groupId>
|
<groupId>com.aliyun.oss</groupId>
|
||||||
<artifactId>aliyun-sdk-oss</artifactId>
|
<artifactId>aliyun-sdk-oss</artifactId>
|
||||||
<version>3.17.4</version>
|
<version>3.17.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 阿里云内容安全服务 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun</groupId>
|
<groupId>com.aliyun</groupId>
|
||||||
<artifactId>green20220302</artifactId>
|
<artifactId>green20220302</artifactId>
|
||||||
<version>3.0.1</version>
|
<version>3.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- FastJSON JSON解析库 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba</groupId>
|
<groupId>com.alibaba</groupId>
|
||||||
<artifactId>fastjson</artifactId>
|
<artifactId>fastjson</artifactId>
|
||||||
<version>1.2.83</version>
|
<version>1.2.83</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 阿里云Java SDK核心库 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun</groupId>
|
<groupId>com.aliyun</groupId>
|
||||||
<artifactId>aliyun-java-sdk-core</artifactId>
|
<artifactId>aliyun-java-sdk-core</artifactId>
|
||||||
<version>4.6.3</version>
|
<version>4.6.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 阿里云绿色服务SDK -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun</groupId>
|
<groupId>com.aliyun</groupId>
|
||||||
<artifactId>aliyun-java-sdk-green</artifactId>
|
<artifactId>aliyun-java-sdk-green</artifactId>
|
||||||
<version>3.4.2</version>
|
<version>3.4.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 阿里云图像审核服务 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun</groupId>
|
<groupId>com.aliyun</groupId>
|
||||||
<artifactId>imageaudit20191230</artifactId>
|
<artifactId>imageaudit20191230</artifactId>
|
||||||
<version>2.0.6</version>
|
<version>2.0.6</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Tea OpenAPI规范实现 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun</groupId>
|
<groupId>com.aliyun</groupId>
|
||||||
<artifactId>tea-openapi</artifactId>
|
<artifactId>tea-openapi</artifactId>
|
||||||
<version>0.2.8</version>
|
<version>0.2.8</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- JWT 相关依赖 -->
|
<!-- JWT 认证授权相关依赖 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
<artifactId>jjwt-api</artifactId>
|
<artifactId>jjwt-api</artifactId>
|
||||||
@@ -166,18 +184,19 @@
|
|||||||
<version>3.0.0</version>
|
<version>3.0.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Spring Mail 邮件发送 -->
|
<!-- Spring Mail 邮件发送功能 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-mail</artifactId>
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Redis 依赖 -->
|
<!-- Redis 数据缓存依赖(重复定义,可删除) -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- PageHelper 分页插件 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.pagehelper</groupId>
|
<groupId>com.github.pagehelper</groupId>
|
||||||
<artifactId>pagehelper-spring-boot-starter</artifactId>
|
<artifactId>pagehelper-spring-boot-starter</artifactId>
|
||||||
@@ -185,8 +204,10 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
<!-- 依赖管理:统一管理项目中使用的依赖版本 -->
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- Spring Boot 依赖管理 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-dependencies</artifactId>
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
|
|||||||
16
src/main/java/com/bao/dating/config/AliyunOSSConfig.java
Normal file
16
src/main/java/com/bao/dating/config/AliyunOSSConfig.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package com.bao.dating.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "aliyun.oss")
|
||||||
|
public class AliyunOSSConfig {
|
||||||
|
private String endpoint;
|
||||||
|
private String accessKeyId;
|
||||||
|
private String accessKeySecret;
|
||||||
|
private String bucketName;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -14,19 +14,21 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class RedisConfig {
|
public class RedisConfig {
|
||||||
@Bean
|
@Bean
|
||||||
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||||
// 创建RedisTemplate对象
|
// 创建RedisTemplate对象
|
||||||
RedisTemplate redisTemplate = new RedisTemplate<>();
|
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||||
// 设置redis的连接工厂对象
|
// 设置redis的连接工厂对象
|
||||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||||
|
|
||||||
// 设置redis key的序列化器
|
// Key和HashKey都使用String序列化
|
||||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
StringRedisSerializer stringSerializer = new StringRedisSerializer();
|
||||||
// 设置value的序列化器
|
redisTemplate.setKeySerializer(stringSerializer);
|
||||||
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
|
redisTemplate.setHashKeySerializer(stringSerializer);
|
||||||
// 设置hash类型的key和value的序列化器
|
|
||||||
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
// Value和HashValue使用JSON序列化
|
||||||
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
|
GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
|
||||||
|
redisTemplate.setValueSerializer(jsonSerializer);
|
||||||
|
redisTemplate.setHashValueSerializer(jsonSerializer);
|
||||||
|
|
||||||
redisTemplate.afterPropertiesSet();
|
redisTemplate.afterPropertiesSet();
|
||||||
return redisTemplate;
|
return redisTemplate;
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ public class WebConfig implements WebMvcConfigurer {
|
|||||||
"/user/emailLogin",
|
"/user/emailLogin",
|
||||||
"/api/verification/send-email-code",
|
"/api/verification/send-email-code",
|
||||||
"/ip/location",
|
"/ip/location",
|
||||||
"/user/login",
|
"/user/sendCode",
|
||||||
"/user/sendCode"
|
"/download/{postId}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
package com.bao.dating.context;
|
package com.bao.dating.context;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户上下文类,用于保存当前线程的用户ID
|
* 用户上下文类,用于保存当前线程的用户ID和token
|
||||||
* @author lenovo
|
* @author lenovo
|
||||||
*/
|
*/
|
||||||
public class UserContext {
|
public class UserContext {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前线程的用户ID
|
||||||
|
*/
|
||||||
|
|
||||||
private static final ThreadLocal<Long> USER_HOLDER = new ThreadLocal<>();
|
private static final ThreadLocal<Long> USER_HOLDER = new ThreadLocal<>();
|
||||||
|
/**
|
||||||
|
* 当前线程的设备ID
|
||||||
|
*/
|
||||||
|
private static final ThreadLocal<String> DEVICE_HOLDER = new ThreadLocal<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置当前线程的用户ID
|
* 设置当前线程的用户ID
|
||||||
@@ -25,9 +33,26 @@ public class UserContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除当前线程的用户ID
|
* 设置当前线程的设备ID
|
||||||
|
* @param deviceId 设备ID
|
||||||
|
*/
|
||||||
|
public static void setDeviceId(String deviceId) {
|
||||||
|
DEVICE_HOLDER.set(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前线程的设备ID
|
||||||
|
* @return 当前设备ID,如果未设置则返回null
|
||||||
|
*/
|
||||||
|
public static String getDeviceId() {
|
||||||
|
return DEVICE_HOLDER.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除当前线程的用户ID和token
|
||||||
*/
|
*/
|
||||||
public static void clear() {
|
public static void clear() {
|
||||||
USER_HOLDER.remove();
|
USER_HOLDER.remove();
|
||||||
|
DEVICE_HOLDER.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.bao.dating.controller;
|
|||||||
import com.bao.dating.context.UserContext;
|
import com.bao.dating.context.UserContext;
|
||||||
import com.bao.dating.service.ContactsService;
|
import com.bao.dating.service.ContactsService;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
@@ -12,6 +14,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/contacts")
|
||||||
public class ContactsController {
|
public class ContactsController {
|
||||||
@Resource
|
@Resource
|
||||||
private ContactsService contactsService;
|
private ContactsService contactsService;
|
||||||
@@ -42,4 +45,56 @@ public class ContactsController {
|
|||||||
result.put("data", friends);
|
result.put("data", friends);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉黑联系人接口
|
||||||
|
* @param userId 当前用户ID
|
||||||
|
* @param contactUserId 被拉黑联系人ID
|
||||||
|
* @return 接口响应
|
||||||
|
*/
|
||||||
|
@PostMapping("/blacklist/{userId}/{contactUserId}")
|
||||||
|
public ResponseEntity<Map<String, Object>> blacklistContact(
|
||||||
|
@PathVariable Long userId,
|
||||||
|
@PathVariable Long contactUserId) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
boolean success = contactsService.blacklistContact(userId, contactUserId);
|
||||||
|
if (success) {
|
||||||
|
result.put("code", 200);
|
||||||
|
result.put("msg", "拉黑联系人成功");
|
||||||
|
result.put("data", null);
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
} else {
|
||||||
|
result.put("code", 500);
|
||||||
|
result.put("msg", "拉黑联系人失败,联系人不存在或参数错误");
|
||||||
|
result.put("data", null);
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除联系人接口
|
||||||
|
* @param userId 当前用户ID
|
||||||
|
* @param contactUserId 被删除联系人ID
|
||||||
|
* @return 接口响应
|
||||||
|
*/
|
||||||
|
@PostMapping("/delete/{userId}/{contactUserId}")
|
||||||
|
public ResponseEntity<Map<String, Object>> deleteContact(
|
||||||
|
@PathVariable Long userId,
|
||||||
|
@PathVariable Long contactUserId) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
boolean success = contactsService.deleteContact(userId, contactUserId);
|
||||||
|
if (success) {
|
||||||
|
result.put("code", 200);
|
||||||
|
result.put("msg", "删除联系人成功");
|
||||||
|
result.put("data", null);
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
} else {
|
||||||
|
result.put("code", 500);
|
||||||
|
result.put("msg", "删除联系人失败,联系人不存在或参数错误");
|
||||||
|
result.put("data", null);
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -83,4 +86,20 @@ public class PostController {
|
|||||||
PostEditVO result = postService.updatePost(postId, postRequestDTO);
|
PostEditVO result = postService.updatePost(postId, postRequestDTO);
|
||||||
return Result.success(ResultCode.SUCCESS, "动态更新成功", result);
|
return Result.success(ResultCode.SUCCESS, "动态更新成功", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/download/{postId}")
|
||||||
|
public void downloadPostImage(@PathVariable Long postId, HttpServletResponse response) throws Exception {
|
||||||
|
try {
|
||||||
|
//Service 返回已经加好水印的图片
|
||||||
|
BufferedImage image = postService.downloadWithWatermark(postId);
|
||||||
|
//设置响应头,触发浏览器下载
|
||||||
|
response.setContentType("image/jpeg");
|
||||||
|
response.setHeader("Content-Disposition", "attachment;filename=post_" + postId + ".jpg");
|
||||||
|
//输出到浏览器
|
||||||
|
ImageIO.write(image, "jpg", response.getOutputStream());
|
||||||
|
response.getOutputStream().flush();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package com.bao.dating.controller;
|
package com.bao.dating.controller;
|
||||||
|
|
||||||
import com.bao.dating.anno.Log;
|
|
||||||
import com.bao.dating.common.Result;
|
import com.bao.dating.common.Result;
|
||||||
import com.bao.dating.common.ResultCode;
|
import com.bao.dating.common.ResultCode;
|
||||||
import com.bao.dating.context.UserContext;
|
import com.bao.dating.context.UserContext;
|
||||||
|
import com.bao.dating.pojo.dto.UserDeviceDTO;
|
||||||
import com.bao.dating.pojo.dto.UserInfoDTO;
|
import com.bao.dating.pojo.dto.UserInfoDTO;
|
||||||
import com.bao.dating.pojo.dto.UserLoginDTO;
|
import com.bao.dating.pojo.dto.UserLoginDTO;
|
||||||
|
import com.bao.dating.pojo.dto.UserLoginWithDeviceDTO;
|
||||||
|
import com.bao.dating.pojo.vo.UserDeviceVO;
|
||||||
import com.bao.dating.pojo.vo.UserInfoVO;
|
import com.bao.dating.pojo.vo.UserInfoVO;
|
||||||
import com.bao.dating.pojo.vo.UserLoginVO;
|
import com.bao.dating.pojo.vo.UserLoginVO;
|
||||||
import com.bao.dating.service.UserService;
|
import com.bao.dating.service.UserService;
|
||||||
@@ -30,13 +32,13 @@ public class UserController {
|
|||||||
private UserService userService;
|
private UserService userService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录
|
* 简单登录(不记录设备信息)
|
||||||
* @param userLoginDTO 登录参数
|
* @param userLoginDTO 登录参数
|
||||||
*/
|
*/
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO) {
|
public Result<UserLoginVO> simpleLogin(@RequestBody UserLoginDTO userLoginDTO) {
|
||||||
UserLoginVO userloginVO = userService.userLogin(userLoginDTO);
|
UserLoginVO userLoginVO = userService.userLogin(userLoginDTO);
|
||||||
return Result.success(ResultCode.SUCCESS, "登录成功", userloginVO);
|
return Result.success(ResultCode.SUCCESS, "登录成功", userLoginVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,7 +48,8 @@ public class UserController {
|
|||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
public Result<Void> logout(HttpServletRequest request) {
|
public Result<Void> logout(HttpServletRequest request) {
|
||||||
String token = request.getHeader("token");
|
String token = request.getHeader("token");
|
||||||
userService.logout(token);
|
String deviceId = request.getHeader("deviceId");
|
||||||
|
userService.logout(token, deviceId);
|
||||||
return Result.success(ResultCode.SUCCESS,"退出登录成功",null);
|
return Result.success(ResultCode.SUCCESS,"退出登录成功",null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +69,6 @@ public class UserController {
|
|||||||
* @param file 头像文件
|
* @param file 头像文件
|
||||||
* @return 上传后的文件URL列表
|
* @return 上传后的文件URL列表
|
||||||
*/
|
*/
|
||||||
@Log
|
|
||||||
@PostMapping(value = "/info/uploadAvatar", consumes = "multipart/form-data")
|
@PostMapping(value = "/info/uploadAvatar", consumes = "multipart/form-data")
|
||||||
public Result<String> uploadAvatar(@RequestParam("file") MultipartFile file) {
|
public Result<String> uploadAvatar(@RequestParam("file") MultipartFile file) {
|
||||||
String fileUrl = userService.uploadAvatar(file);
|
String fileUrl = userService.uploadAvatar(file);
|
||||||
@@ -78,7 +80,6 @@ public class UserController {
|
|||||||
* @param file 背景文件
|
* @param file 背景文件
|
||||||
* @return 上传后的文件URL列表
|
* @return 上传后的文件URL列表
|
||||||
*/
|
*/
|
||||||
@Log
|
|
||||||
@PostMapping(value = "/info/uploadBackground", consumes = "multipart/form-data")
|
@PostMapping(value = "/info/uploadBackground", consumes = "multipart/form-data")
|
||||||
public Result<String> uploadBackground(@RequestParam("file") MultipartFile file) {
|
public Result<String> uploadBackground(@RequestParam("file") MultipartFile file) {
|
||||||
String fileUrl = userService.uploadBackground(file);
|
String fileUrl = userService.uploadBackground(file);
|
||||||
@@ -90,7 +91,6 @@ public class UserController {
|
|||||||
* @param userInfoUpdateDTO 用户信息更新参数
|
* @param userInfoUpdateDTO 用户信息更新参数
|
||||||
* @return 更新后的用户信息
|
* @return 更新后的用户信息
|
||||||
*/
|
*/
|
||||||
@Log
|
|
||||||
@PostMapping("/info/update")
|
@PostMapping("/info/update")
|
||||||
public Result<UserInfoVO> userInfoUpdate(@RequestBody UserInfoDTO userInfoUpdateDTO) {
|
public Result<UserInfoVO> userInfoUpdate(@RequestBody UserInfoDTO userInfoUpdateDTO) {
|
||||||
Long userId = UserContext.getUserId();
|
Long userId = UserContext.getUserId();
|
||||||
@@ -214,4 +214,33 @@ public class UserController {
|
|||||||
boolean online = userService.isUserOnline(userId);
|
boolean online = userService.isUserOnline(userId);
|
||||||
return Result.success(ResultCode.SUCCESS, "查询成功", online);
|
return Result.success(ResultCode.SUCCESS, "查询成功", online);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取客户端IP地址
|
||||||
|
* @param request 请求
|
||||||
|
* @return IP地址
|
||||||
|
*/
|
||||||
|
private String getClientIp(HttpServletRequest request) {
|
||||||
|
String ip = request.getHeader("X-Forwarded-For");
|
||||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("Proxy-Client-IP");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("HTTP_CLIENT_IP");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getRemoteAddr();
|
||||||
|
}
|
||||||
|
// 多个代理时,第一个IP为真实IP
|
||||||
|
if (ip != null && ip.contains(",")) {
|
||||||
|
ip = ip.split(",")[0].trim();
|
||||||
|
}
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ import com.bao.dating.context.UserContext;
|
|||||||
import com.bao.dating.util.JwtUtil;
|
import com.bao.dating.util.JwtUtil;
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.method.HandlerMethod;
|
import org.springframework.web.method.HandlerMethod;
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HttpToken拦截器类
|
* HttpToken拦截器类
|
||||||
* 用于拦截请求并验证JWT token的有效性,同时从token中解析用户信息
|
* 用于拦截请求并验证JWT token的有效性,同时从token中解析用户信息
|
||||||
@@ -25,6 +28,7 @@ public class TokenInterceptor implements HandlerInterceptor {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private RedisTemplate redisTemplate;
|
private RedisTemplate redisTemplate;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在请求处理之前进行拦截
|
* 在请求处理之前进行拦截
|
||||||
* 从请求头或URL参数中获取token,验证其有效性,并将用户ID保存到ThreadLocal中
|
* 从请求头或URL参数中获取token,验证其有效性,并将用户ID保存到ThreadLocal中
|
||||||
@@ -42,28 +46,35 @@ public class TokenInterceptor implements HandlerInterceptor {
|
|||||||
//当前拦截到的不是动态方法,直接放行
|
//当前拦截到的不是动态方法,直接放行
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从 header 获取 token
|
// 从 header 获取 token
|
||||||
String token = request.getHeader("token");
|
String token = request.getHeader("token");
|
||||||
|
if (StringUtils.isBlank(token)) {
|
||||||
|
write401(response, "未登录,请先登录");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 deviceId(多设备关键)
|
||||||
|
String deviceId = request.getHeader("deviceId");
|
||||||
|
if (StringUtils.isBlank(deviceId)) {
|
||||||
|
write401(response, "设备标识缺失");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
log.info("jwt校验: {}", token);
|
log.info("HTTP鉴权 token={}, deviceId={}", token, deviceId);
|
||||||
|
|
||||||
// 验证 token 是否有效(包括是否过期)
|
// 验证 token 是否有效(包括是否过期)
|
||||||
if (!JwtUtil.validateToken(token)) {
|
if (!JwtUtil.validateToken(token)) {
|
||||||
log.error("Token无效或已过期");
|
write401(response, "Token无效或已过期");
|
||||||
response.setStatus(401);
|
|
||||||
response.setContentType("application/json;charset=UTF-8");
|
|
||||||
response.getWriter().write("Token无效或已过期");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查 token 是否在黑名单中
|
// 检查 token 是否在黑名单中
|
||||||
Object blacklistToken = redisTemplate.opsForValue().get("jwt:blacklist:" + token);
|
Object blacklistToken = redisTemplate.opsForValue().get("jwt:blacklist:" + token);
|
||||||
if (blacklistToken != null) {
|
if (blacklistToken != null) {
|
||||||
log.error("Token已在黑名单中");
|
write401(response, "登录已失效,请重新登录");
|
||||||
response.setStatus(401);
|
|
||||||
response.setContentType("application/json;charset=UTF-8");
|
|
||||||
response.getWriter().write("登录已失效, 请重新登录");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +85,6 @@ public class TokenInterceptor implements HandlerInterceptor {
|
|||||||
String banKey = "user:ban:" + userId;
|
String banKey = "user:ban:" + userId;
|
||||||
if (Boolean.TRUE.equals(redisTemplate.hasKey(banKey))) {
|
if (Boolean.TRUE.equals(redisTemplate.hasKey(banKey))) {
|
||||||
String reason = String.valueOf(redisTemplate.opsForValue().get(banKey));
|
String reason = String.valueOf(redisTemplate.opsForValue().get(banKey));
|
||||||
log.error("用户 {} 已被封禁,原因:{}", userId, reason);
|
|
||||||
|
|
||||||
response.setStatus(403);
|
response.setStatus(403);
|
||||||
response.setContentType("application/json;charset=UTF-8");
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
@@ -82,22 +92,19 @@ public class TokenInterceptor implements HandlerInterceptor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从Redis获取存储的token进行比对
|
// 多设备 token 校验
|
||||||
Object redisTokenObj = redisTemplate.opsForValue().get("login:token:" + userId);
|
String redisTokenKey = "login:token:" + userId + ":" + deviceId;
|
||||||
String redisToken = redisTokenObj != null ? redisTokenObj.toString() : null;
|
Object redisTokenObj = redisTemplate.opsForValue().get(redisTokenKey);
|
||||||
|
|
||||||
// 验证Redis中的token是否存在且匹配
|
if (redisTokenObj == null || !token.equals(redisTokenObj.toString())) {
|
||||||
if (redisToken == null || !redisToken.equals(token)) {
|
write401(response, "登录状态已失效");
|
||||||
log.error("登录已失效");
|
|
||||||
response.setStatus(401);
|
|
||||||
response.setContentType("application/json;charset=UTF-8");
|
|
||||||
response.getWriter().write("登录已失效");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("用户: {}", userId);
|
// 保存 登录信息 到 ThreadLocal
|
||||||
// 保存 userId 到 ThreadLocal
|
|
||||||
UserContext.setUserId(userId);
|
UserContext.setUserId(userId);
|
||||||
|
UserContext.setDeviceId(deviceId);
|
||||||
|
log.info("token验证成功 userId={}, deviceId={}", userId, deviceId);
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Token 校验失败: {}", e.getMessage());
|
log.error("Token 校验失败: {}", e.getMessage());
|
||||||
@@ -120,4 +127,16 @@ public class TokenInterceptor implements HandlerInterceptor {
|
|||||||
UserContext.clear();
|
UserContext.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应错误信息
|
||||||
|
* @param response
|
||||||
|
* @param msg
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private void write401(HttpServletResponse response, String msg) throws IOException {
|
||||||
|
response.setStatus(401);
|
||||||
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
|
response.getWriter().write(msg);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -42,9 +42,10 @@ public class WsAuthInterceptor implements HandshakeInterceptor {
|
|||||||
|
|
||||||
// 从URL参数中获取token
|
// 从URL参数中获取token
|
||||||
String token = servletRequest.getParameter("token");
|
String token = servletRequest.getParameter("token");
|
||||||
|
String deviceId = servletRequest.getParameter("deviceId");
|
||||||
|
|
||||||
if (StringUtils.isBlank(token)) {
|
if (StringUtils.isBlank(token) || StringUtils.isBlank(deviceId)) {
|
||||||
log.error("WebSocket握手失败:令牌丢失");
|
log.error("WebSocket认证失败:token或deviceId缺失");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,21 +82,19 @@ public class WsAuthInterceptor implements HandshakeInterceptor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从Redis获取存储的token进行比对
|
// 多设备 token 校验
|
||||||
String redisTokenKey = "login:token:" + userId;
|
String redisTokenKey = "login:token:" + userId + ":" + deviceId;
|
||||||
Object redisTokenObj = redisTemplate.opsForValue().get(redisTokenKey);
|
Object redisTokenObj = redisTemplate.opsForValue().get(redisTokenKey);
|
||||||
String redisToken = redisTokenObj != null ? redisTokenObj.toString() : null;
|
|
||||||
log.info("Redis中存储的token: {}", redisToken != null ? "存在" : "不存在");
|
|
||||||
|
|
||||||
// 验证Redis中的token是否存在且匹配
|
if (redisTokenObj == null || !token.equals(redisTokenObj.toString())) {
|
||||||
if (redisToken == null || !redisToken.equals(token)) {
|
log.error("登录已失效");
|
||||||
log.error("登录已失效 - Redis中token不存在或不匹配");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("WebSocket认证成功,用户ID: {}", userId);
|
// 将信息保存到attributes中
|
||||||
// 将用户ID保存到attributes中
|
|
||||||
attributes.put("userId", userId);
|
attributes.put("userId", userId);
|
||||||
|
attributes.put("deviceId", deviceId);
|
||||||
|
log.info("WebSocket认证成功 userId={}, deviceId={}", userId, deviceId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (NumberFormatException e) {
|
catch (NumberFormatException e) {
|
||||||
|
|||||||
@@ -17,4 +17,20 @@ public interface ContactMapper {
|
|||||||
*/
|
*/
|
||||||
List<Map<String, Object>> selectFriendsByUserId(@Param("userId") Long userId);
|
List<Map<String, Object>> selectFriendsByUserId(@Param("userId") Long userId);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉黑联系人
|
||||||
|
* @param userId 当前用户ID
|
||||||
|
* @param contactUserId 被拉黑的联系人ID
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int blacklistContact(@Param("userId") Long userId, @Param("contactUserId") Long contactUserId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除联系人(逻辑删除)
|
||||||
|
* @param userId 当前用户ID
|
||||||
|
* @param contactUserId 被删除的联系人ID
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int deleteContact(@Param("userId") Long userId, @Param("contactUserId") Long contactUserId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.apache.ibatis.annotations.Mapper;
|
|||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 动态Mapper
|
* 动态Mapper
|
||||||
@@ -117,4 +118,12 @@ public interface PostMapper {
|
|||||||
* @return 影响行数
|
* @return 影响行数
|
||||||
*/
|
*/
|
||||||
int decreaseFavoriteCount(Long postId);
|
int decreaseFavoriteCount(Long postId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据动态id查询用户名和媒体信息
|
||||||
|
*
|
||||||
|
* @param postId 动态id
|
||||||
|
* @return 用户名和媒体信息
|
||||||
|
*/
|
||||||
|
Map<String, Object> getUsernameByUserId(Long postId);
|
||||||
}
|
}
|
||||||
|
|||||||
51
src/main/java/com/bao/dating/pojo/dto/UserDeviceDTO.java
Normal file
51
src/main/java/com/bao/dating/pojo/dto/UserDeviceDTO.java
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package com.bao.dating.pojo.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户设备DTO
|
||||||
|
* @author KilLze
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UserDeviceDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备ID(唯一标识)
|
||||||
|
*/
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备类型:1-Android, 2-iOS, 3-Web, 4-其他
|
||||||
|
*/
|
||||||
|
private Integer deviceType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备名称(如:iPhone 14 Pro)
|
||||||
|
*/
|
||||||
|
private String deviceName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备品牌(如:Apple, Xiaomi)
|
||||||
|
*/
|
||||||
|
private String deviceBrand;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作系统版本
|
||||||
|
*/
|
||||||
|
private String osVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 浏览器/应用版本
|
||||||
|
*/
|
||||||
|
private String appVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备IP
|
||||||
|
*/
|
||||||
|
private String ipAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备位置(根据IP解析,可选)
|
||||||
|
*/
|
||||||
|
private String location;
|
||||||
|
}
|
||||||
@@ -12,4 +12,10 @@ import java.io.Serializable;
|
|||||||
public class UserLoginDTO implements Serializable {
|
public class UserLoginDTO implements Serializable {
|
||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
|
/** 设备唯一标识(前端生成)*/
|
||||||
|
private String deviceId;
|
||||||
|
/** 设备类型 */
|
||||||
|
private String deviceType;
|
||||||
|
/** 设备名称 */
|
||||||
|
private String deviceName;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.bao.dating.pojo.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录请求DTO(带设备信息)
|
||||||
|
* @author KilLze
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UserLoginWithDeviceDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
*/
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备ID(唯一标识)
|
||||||
|
*/
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备类型:1-Android, 2-iOS, 3-Web, 4-其他
|
||||||
|
*/
|
||||||
|
private Integer deviceType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备名称(如:iPhone 14 Pro)
|
||||||
|
*/
|
||||||
|
private String deviceName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备品牌(如:Apple, Xiaomi)
|
||||||
|
*/
|
||||||
|
private String deviceBrand;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作系统版本
|
||||||
|
*/
|
||||||
|
private String osVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 浏览器/应用版本
|
||||||
|
*/
|
||||||
|
private String appVersion;
|
||||||
|
}
|
||||||
78
src/main/java/com/bao/dating/pojo/vo/UserDeviceVO.java
Normal file
78
src/main/java/com/bao/dating/pojo/vo/UserDeviceVO.java
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package com.bao.dating.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户设备信息VO
|
||||||
|
* @author KilLze
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UserDeviceVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备ID
|
||||||
|
*/
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备类型:1-Android, 2-iOS, 3-Web, 4-其他
|
||||||
|
*/
|
||||||
|
private String deviceType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备名称
|
||||||
|
*/
|
||||||
|
private String deviceName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备品牌
|
||||||
|
*/
|
||||||
|
private String deviceBrand;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作系统版本
|
||||||
|
*/
|
||||||
|
private String osVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用版本
|
||||||
|
*/
|
||||||
|
private String appVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IP地址
|
||||||
|
*/
|
||||||
|
private String ipAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备位置
|
||||||
|
*/
|
||||||
|
private String location;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否当前设备
|
||||||
|
*/
|
||||||
|
private Boolean isCurrent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否在线
|
||||||
|
*/
|
||||||
|
private Boolean isOnline;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后活跃时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime lastActiveAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime loginAt;
|
||||||
|
}
|
||||||
@@ -12,4 +12,5 @@ public class UserLoginVO implements Serializable {
|
|||||||
private Long userId;
|
private Long userId;
|
||||||
private String nickname;
|
private String nickname;
|
||||||
private String token;
|
private String token;
|
||||||
|
private String deviceId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,20 @@ public interface ContactsService {
|
|||||||
List<Map<String, Object>> getFriendsByUserId(Long userId);
|
List<Map<String, Object>> getFriendsByUserId(Long userId);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉黑联系人
|
||||||
|
* @param userId 当前用户ID
|
||||||
|
* @param contactUserId 被拉黑联系人ID
|
||||||
|
* @return 操作是否成功
|
||||||
|
*/
|
||||||
|
boolean blacklistContact(Long userId, Long contactUserId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除联系人(逻辑删除)
|
||||||
|
* @param userId 当前用户ID
|
||||||
|
* @param contactUserId 被删除联系人ID
|
||||||
|
* @return 操作是否成功
|
||||||
|
*/
|
||||||
|
boolean deleteContact(Long userId, Long contactUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.bao.dating.pojo.entity.Post;
|
|||||||
import com.bao.dating.pojo.vo.PostEditVO;
|
import com.bao.dating.pojo.vo.PostEditVO;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,4 +56,11 @@ public interface PostService {
|
|||||||
* @return 用户id
|
* @return 用户id
|
||||||
*/
|
*/
|
||||||
Long selectUserIdByPostId(Long postId);
|
Long selectUserIdByPostId(Long postId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载动态图片并添加水印
|
||||||
|
* @param postId 动态ID
|
||||||
|
* @return 带水印的图片
|
||||||
|
*/
|
||||||
|
BufferedImage downloadWithWatermark(Long postId) throws Exception;
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ public interface UserService {
|
|||||||
* @param token 登录凭证
|
* @param token 登录凭证
|
||||||
* @return 注册结果
|
* @return 注册结果
|
||||||
*/
|
*/
|
||||||
void logout(String token);
|
void logout(String token, String deviceId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询个人信息
|
* 查询个人信息
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.bao.dating.service.ContactsService;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -19,4 +20,34 @@ public class ContactServiceImpl implements ContactsService {
|
|||||||
// 直接调用Mapper查询,无额外封装
|
// 直接调用Mapper查询,无额外封装
|
||||||
return contactMapper.selectFriendsByUserId(userId);
|
return contactMapper.selectFriendsByUserId(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉黑联系人
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean blacklistContact(Long userId, Long contactUserId) {
|
||||||
|
// 参数校验
|
||||||
|
if (userId == null || contactUserId == null || userId.equals(contactUserId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 执行拉黑操作
|
||||||
|
int affectRows = contactMapper.blacklistContact(userId, contactUserId);
|
||||||
|
return affectRows > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除联系人
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean deleteContact(Long userId, Long contactUserId) {
|
||||||
|
// 参数校验
|
||||||
|
if (userId == null || contactUserId == null || userId.equals(contactUserId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 执行删除操作
|
||||||
|
int affectRows = contactMapper.deleteContact(userId, contactUserId);
|
||||||
|
return affectRows > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ import com.bao.dating.mapper.PostLikeMapper;
|
|||||||
import com.bao.dating.mapper.PostMapper;
|
import com.bao.dating.mapper.PostMapper;
|
||||||
import com.bao.dating.pojo.dto.PostRequestDTO;
|
import com.bao.dating.pojo.dto.PostRequestDTO;
|
||||||
import com.bao.dating.pojo.entity.Post;
|
import com.bao.dating.pojo.entity.Post;
|
||||||
|
import com.bao.dating.pojo.entity.User;
|
||||||
import com.bao.dating.pojo.vo.PostEditVO;
|
import com.bao.dating.pojo.vo.PostEditVO;
|
||||||
import com.bao.dating.service.PostService;
|
import com.bao.dating.service.PostService;
|
||||||
import com.bao.dating.common.aliyun.AliOssUtil;
|
import com.bao.dating.common.aliyun.AliOssUtil;
|
||||||
import com.bao.dating.service.UserService;
|
import com.bao.dating.service.UserService;
|
||||||
import com.bao.dating.util.FileUtil;
|
import com.bao.dating.util.FileUtil;
|
||||||
|
import com.bao.dating.util.WatermarkUtil;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -22,6 +24,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -57,6 +60,9 @@ public class PostServiceImpl implements PostService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private CommentsMapper commentsMapper;
|
private CommentsMapper commentsMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WatermarkUtil watermarkUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传媒体文件
|
* 上传媒体文件
|
||||||
* @param files 媒体文件数组
|
* @param files 媒体文件数组
|
||||||
@@ -344,4 +350,61 @@ public class PostServiceImpl implements PostService {
|
|||||||
return postMapper.selectUserIdByPostId(postId);
|
return postMapper.selectUserIdByPostId(postId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载动态图片并添加水印
|
||||||
|
*
|
||||||
|
* @param postId 动态ID
|
||||||
|
* @return 带水印的图片
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public BufferedImage downloadWithWatermark(Long postId) throws Exception {
|
||||||
|
// 一次性查出 动态图片 + 作者信息
|
||||||
|
Map<String, Object> map = postMapper.getUsernameByUserId(postId);
|
||||||
|
|
||||||
|
if (map == null || map.isEmpty()) {
|
||||||
|
Post post = postMapper.selectById(postId);
|
||||||
|
if (post == null) {
|
||||||
|
throw new RuntimeException("未找到指定postId的帖子: " + postId);
|
||||||
|
}
|
||||||
|
throw new RuntimeException("未找到与postId相关的用户和媒体信息: " + postId);
|
||||||
|
}
|
||||||
|
|
||||||
|
String mediaUrl = (String) map.get("media_oss_keys");
|
||||||
|
String username = (String) map.get("user_name");
|
||||||
|
Object userIdObj = map.get("user_id");
|
||||||
|
|
||||||
|
if (mediaUrl == null || username == null || userIdObj == null) {
|
||||||
|
throw new RuntimeException("用户或媒体信息不完整: " + map);
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaUrl = mediaUrl.trim();
|
||||||
|
if (mediaUrl.isEmpty()) {
|
||||||
|
throw new RuntimeException("媒体URL为空,postId: " + postId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Long userId = userIdObj instanceof Number
|
||||||
|
? ((Number) userIdObj).longValue()
|
||||||
|
: Long.valueOf(userIdObj.toString());
|
||||||
|
|
||||||
|
// 解析 OSS ObjectKey(支持完整URL和直接存key两种)
|
||||||
|
String cleanUrl = mediaUrl.split("\\?")[0]; // 去掉 ? 后面的参数
|
||||||
|
String objectKey;
|
||||||
|
|
||||||
|
if (cleanUrl.startsWith("http")) {
|
||||||
|
// https://xxx.oss-cn-xxx.aliyuncs.com/post/xxx.jpg → post/xxx.jpg
|
||||||
|
objectKey = cleanUrl.substring(cleanUrl.indexOf(".com/") + 5);
|
||||||
|
} else {
|
||||||
|
objectKey = cleanUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objectKey.trim().isEmpty()) {
|
||||||
|
throw new RuntimeException("解析后的ObjectKey为空,url: " + mediaUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载并动态加水印(只给下载的人看,OSS原图不改,数据库不动)
|
||||||
|
return watermarkUtil.downloadAndWatermark(objectKey, username, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -82,6 +82,9 @@ public class UserServiceImpl implements UserService {
|
|||||||
if (userLoginDTO == null || userLoginDTO.getUsername() == null || userLoginDTO.getPassword() == null) {
|
if (userLoginDTO == null || userLoginDTO.getUsername() == null || userLoginDTO.getPassword() == null) {
|
||||||
throw new RuntimeException("用户名或密码不能为空");
|
throw new RuntimeException("用户名或密码不能为空");
|
||||||
}
|
}
|
||||||
|
if (userLoginDTO.getDeviceId() == null || userLoginDTO.getDeviceName() == null || userLoginDTO.getDeviceType() == null){
|
||||||
|
throw new RuntimeException("未获取到设备");
|
||||||
|
}
|
||||||
// 查询用户
|
// 查询用户
|
||||||
User user = userMapper.getByUsername(userLoginDTO.getUsername());
|
User user = userMapper.getByUsername(userLoginDTO.getUsername());
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
@@ -101,19 +104,40 @@ public class UserServiceImpl implements UserService {
|
|||||||
// 生成token
|
// 生成token
|
||||||
String token = JwtUtil.generateToken(String.valueOf(user.getUserId()));
|
String token = JwtUtil.generateToken(String.valueOf(user.getUserId()));
|
||||||
|
|
||||||
String redisKey = "login:token:" + user.getUserId();
|
Long userId = user.getUserId();
|
||||||
|
String deviceId = userLoginDTO.getDeviceId();
|
||||||
|
|
||||||
|
// 缓存登录token
|
||||||
|
String tokenKey = "login:token:" + userId+ ":" + deviceId;
|
||||||
redisTemplate.opsForValue().set(
|
redisTemplate.opsForValue().set(
|
||||||
redisKey,
|
tokenKey,
|
||||||
token,
|
token,
|
||||||
7,
|
7,
|
||||||
TimeUnit.DAYS
|
TimeUnit.DAYS
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 设备信息 Hash
|
||||||
|
String deviceKey = "user:device:" + userId+ ":" + deviceId;
|
||||||
|
Map<String, Object> deviceInfo = new HashMap<>();
|
||||||
|
deviceInfo.put("token", token);
|
||||||
|
deviceInfo.put("deviceType", userLoginDTO.getDeviceType());
|
||||||
|
deviceInfo.put("deviceName", userLoginDTO.getDeviceName());
|
||||||
|
deviceInfo.put("loginTime", System.currentTimeMillis());
|
||||||
|
|
||||||
|
// 存储设备信息
|
||||||
|
redisTemplate.opsForHash().putAll(deviceKey, deviceInfo);
|
||||||
|
redisTemplate.expire(deviceKey, 7, TimeUnit.DAYS);
|
||||||
|
|
||||||
|
// 缓存用户设备信息
|
||||||
|
String deviceSetKey = "user:devices:" + userId;
|
||||||
|
redisTemplate.opsForSet().add(deviceSetKey, deviceId);
|
||||||
|
|
||||||
// 封装返回
|
// 封装返回
|
||||||
UserLoginVO userLoginVO = new UserLoginVO();
|
UserLoginVO userLoginVO = new UserLoginVO();
|
||||||
userLoginVO.setUserId(user.getUserId());
|
userLoginVO.setUserId(userId);
|
||||||
userLoginVO.setNickname(user.getNickname());
|
userLoginVO.setNickname(user.getNickname());
|
||||||
userLoginVO.setToken(token);
|
userLoginVO.setToken(token);
|
||||||
|
userLoginVO.setDeviceId(deviceId);
|
||||||
return userLoginVO;
|
return userLoginVO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,10 +146,10 @@ public class UserServiceImpl implements UserService {
|
|||||||
* @param token 登录凭证
|
* @param token 登录凭证
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void logout(String token) {
|
public void logout(String token, String deviceId) {
|
||||||
Claims claims = JwtUtil.getClaimsFromToken(token);
|
Claims claims = JwtUtil.getClaimsFromToken(token);
|
||||||
// 获取token信息
|
// 获取token信息
|
||||||
String subject = claims.getSubject();
|
String userId = claims.getSubject();
|
||||||
// 获取token的过期时间
|
// 获取token的过期时间
|
||||||
Date expiration = claims.getExpiration();
|
Date expiration = claims.getExpiration();
|
||||||
// 判断 token 是否已过期
|
// 判断 token 是否已过期
|
||||||
@@ -135,10 +159,15 @@ public class UserServiceImpl implements UserService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从Redis中删除登录token记录
|
// 从Redis中删除当前设备登录的token记录
|
||||||
String loginTokenKey = "login:token:" + subject;
|
String loginTokenKey = "login:token:" + userId + ":" + deviceId;
|
||||||
redisTemplate.delete(loginTokenKey);
|
redisTemplate.delete(loginTokenKey);
|
||||||
|
|
||||||
|
// 删除设备信息
|
||||||
|
String deviceKey = "user:device:" + userId + ":" + deviceId;
|
||||||
|
redisTemplate.delete(deviceKey);
|
||||||
|
|
||||||
|
// 将token加入黑名单
|
||||||
String logoutKey = "jwt:blacklist:" + token;
|
String logoutKey = "jwt:blacklist:" + token;
|
||||||
redisTemplate.opsForValue().set(
|
redisTemplate.opsForValue().set(
|
||||||
logoutKey,
|
logoutKey,
|
||||||
@@ -499,6 +528,11 @@ public class UserServiceImpl implements UserService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断用户是否在线
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return true: 在线,false: 离线
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isUserOnline(Long userId) {
|
public boolean isUserOnline(Long userId) {
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
|
|||||||
64
src/main/java/com/bao/dating/util/WatermarkUtil.java
Normal file
64
src/main/java/com/bao/dating/util/WatermarkUtil.java
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package com.bao.dating.util;
|
||||||
|
|
||||||
|
import com.aliyun.oss.OSS;
|
||||||
|
import com.aliyun.oss.OSSClientBuilder;
|
||||||
|
import com.bao.dating.config.AliyunOSSConfig;
|
||||||
|
import com.bao.dating.mapper.PostMapper;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WatermarkUtil {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AliyunOSSConfig aliyunOSSConfig;
|
||||||
|
|
||||||
|
|
||||||
|
public BufferedImage downloadAndWatermark(String objectKey, String username, Long userId) throws Exception {
|
||||||
|
OSS ossClient = new OSSClientBuilder().build(
|
||||||
|
aliyunOSSConfig.getEndpoint(),
|
||||||
|
aliyunOSSConfig.getAccessKeyId(),
|
||||||
|
aliyunOSSConfig.getAccessKeySecret()
|
||||||
|
);
|
||||||
|
|
||||||
|
InputStream inputStream = ossClient.getObject(aliyunOSSConfig.getBucketName(), objectKey).getObjectContent();
|
||||||
|
BufferedImage image = ImageIO.read(inputStream);
|
||||||
|
Graphics2D g2d = image.createGraphics();
|
||||||
|
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||||
|
|
||||||
|
// 字体小一点
|
||||||
|
Font font = new Font("微软雅黑", Font.BOLD, 24);
|
||||||
|
g2d.setFont(font);
|
||||||
|
|
||||||
|
String text = "作者:" + username + " (ID:" + userId + ")";
|
||||||
|
|
||||||
|
FontMetrics fm = g2d.getFontMetrics();
|
||||||
|
int textWidth = fm.stringWidth(text);
|
||||||
|
int textHeight = fm.getHeight();
|
||||||
|
|
||||||
|
// 右下角留边距
|
||||||
|
int x = image.getWidth() - textWidth - 20;
|
||||||
|
int y = image.getHeight() - 20;
|
||||||
|
|
||||||
|
// 黑色描边
|
||||||
|
g2d.setColor(Color.BLACK);
|
||||||
|
g2d.drawString(text, x - 1, y - 1);
|
||||||
|
g2d.drawString(text, x + 1, y - 1);
|
||||||
|
g2d.drawString(text, x - 1, y + 1);
|
||||||
|
g2d.drawString(text, x + 1, y + 1);
|
||||||
|
|
||||||
|
// 白色正文
|
||||||
|
g2d.setColor(Color.WHITE);
|
||||||
|
g2d.drawString(text, x, y);
|
||||||
|
|
||||||
|
g2d.dispose();
|
||||||
|
ossClient.shutdown();
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,5 +21,29 @@
|
|||||||
AND c.relation_type != 3 -- 排除黑名单
|
AND c.relation_type != 3 -- 排除黑名单
|
||||||
AND c.user_id != c.contact_user_id -- 排除自己
|
AND c.user_id != c.contact_user_id -- 排除自己
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 拉黑联系人:更新relation_type为3(黑名单)、contact_status为3(已拉黑) -->
|
||||||
|
<update id="blacklistContact">
|
||||||
|
UPDATE contacts
|
||||||
|
SET relation_type = 3,
|
||||||
|
contact_status = 3,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE user_id = #{userId}
|
||||||
|
AND contact_user_id = #{contactUserId}
|
||||||
|
AND contact_status != 2
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<!-- 删除联系人:更新contact_status为2(已删除) -->
|
||||||
|
<update id="deleteContact">
|
||||||
|
UPDATE contacts
|
||||||
|
SET contact_status = 2,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE user_id = #{userId}
|
||||||
|
AND contact_user_id = #{contactUserId}
|
||||||
|
AND contact_status != 2
|
||||||
|
</update>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -135,5 +135,11 @@
|
|||||||
<select id="selectFavoriteCount" resultType="java.lang.Integer">
|
<select id="selectFavoriteCount" resultType="java.lang.Integer">
|
||||||
select dating.post.favorite_count from dating.post where post.post_id = #{postId}
|
select dating.post.favorite_count from dating.post where post.post_id = #{postId}
|
||||||
</select>
|
</select>
|
||||||
|
<select id="getUsernameByUserId" resultType="map">
|
||||||
|
SELECT u.user_name, u.user_id, p.media_oss_keys
|
||||||
|
FROM post p
|
||||||
|
LEFT JOIN user u ON p.user_id = u.user_id
|
||||||
|
WHERE p.post_id = #{postId}
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<?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.bao.dating.mapper.UserDeviceMapper">
|
||||||
|
|
||||||
|
<!--插入设备信息-->
|
||||||
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
INSERT INTO user_device (
|
||||||
|
user_id, device_id, device_type, device_name, device_brand,
|
||||||
|
os_version, app_version, ip_address, location, token,
|
||||||
|
status, is_current, last_active_at, login_at, created_at
|
||||||
|
) VALUES (
|
||||||
|
#{userId}, #{deviceId}, #{deviceType}, #{deviceName}, #{deviceBrand},
|
||||||
|
#{osVersion}, #{appVersion}, #{ipAddress}, #{location}, #{token},
|
||||||
|
#{status}, #{isCurrent}, #{lastActiveAt}, #{loginAt}, NOW()
|
||||||
|
)
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<!--根据设备ID查询-->
|
||||||
|
<select id="selectByDeviceId" resultType="com.bao.dating.pojo.entity.UserDevice">
|
||||||
|
SELECT * FROM user_device WHERE device_id = #{deviceId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!--根据用户ID查询所有设备-->
|
||||||
|
<select id="selectByUserId" resultType="com.bao.dating.pojo.vo.UserDeviceVO">
|
||||||
|
SELECT
|
||||||
|
id, device_id, device_type, device_name, device_brand,
|
||||||
|
os_version, app_version, ip_address, location,
|
||||||
|
is_current = 1 as is_current,
|
||||||
|
status = 1 as is_online,
|
||||||
|
last_active_at, login_at
|
||||||
|
FROM user_device
|
||||||
|
WHERE user_id = #{userId}
|
||||||
|
ORDER BY is_current DESC, last_active_at DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!--更新设备状态-->
|
||||||
|
<update id="updateStatus">
|
||||||
|
UPDATE user_device SET status = #{status} WHERE device_id = #{deviceId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<!--更新最后活跃时间-->
|
||||||
|
<update id="updateLastActiveAt">
|
||||||
|
UPDATE user_device SET last_active_at = NOW() WHERE device_id = #{deviceId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<!--清除用户当前设备标记-->
|
||||||
|
<update id="clearCurrentDevice">
|
||||||
|
UPDATE user_device SET is_current = 0 WHERE user_id = #{userId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<!--设置当前设备-->
|
||||||
|
<update id="setCurrentDevice">
|
||||||
|
UPDATE user_device SET is_current = 1 WHERE device_id = #{deviceId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<!--根据Token查询-->
|
||||||
|
<select id="selectByToken" resultType="com.bao.dating.pojo.entity.UserDevice">
|
||||||
|
SELECT * FROM user_device WHERE token = #{token}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!--更新设备信息-->
|
||||||
|
<update id="update" parameterType="com.bao.dating.pojo.entity.UserDevice">
|
||||||
|
UPDATE user_device SET
|
||||||
|
device_name = #{deviceName},
|
||||||
|
device_brand = #{deviceBrand},
|
||||||
|
os_version = #{osVersion},
|
||||||
|
app_version = #{appVersion},
|
||||||
|
ip_address = #{ipAddress},
|
||||||
|
location = #{location},
|
||||||
|
last_active_at = NOW()
|
||||||
|
WHERE device_id = #{deviceId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<!--删除设备-->
|
||||||
|
<delete id="deleteByDeviceId">
|
||||||
|
DELETE FROM user_device WHERE device_id = #{deviceId}
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<!--删除用户所有设备-->
|
||||||
|
<delete id="deleteByUserId">
|
||||||
|
DELETE FROM user_device WHERE user_id = #{userId}
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
22
src/main/resources/db/user_device.sql
Normal file
22
src/main/resources/db/user_device.sql
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
-- 用户设备登录记录表
|
||||||
|
CREATE TABLE IF NOT EXISTS `user_device` (
|
||||||
|
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`user_id` BIGINT NOT NULL COMMENT '用户ID',
|
||||||
|
`device_id` VARCHAR(64) NOT NULL COMMENT '设备唯一标识',
|
||||||
|
`device_type` TINYINT DEFAULT 4 COMMENT '设备类型:1-Android, 2-iOS, 3-Web, 4-其他',
|
||||||
|
`device_name` VARCHAR(128) DEFAULT NULL COMMENT '设备名称',
|
||||||
|
`device_brand` VARCHAR(64) DEFAULT NULL COMMENT '设备品牌',
|
||||||
|
`os_version` VARCHAR(32) DEFAULT NULL COMMENT '操作系统版本',
|
||||||
|
`app_version` VARCHAR(32) DEFAULT NULL COMMENT '应用版本',
|
||||||
|
`ip_address` VARCHAR(64) DEFAULT NULL COMMENT 'IP地址',
|
||||||
|
`location` VARCHAR(128) DEFAULT NULL COMMENT '设备位置',
|
||||||
|
`token` VARCHAR(512) DEFAULT NULL COMMENT '登录Token',
|
||||||
|
`status` TINYINT DEFAULT 1 COMMENT '在线状态:1-在线, 0-离线',
|
||||||
|
`is_current` TINYINT DEFAULT 0 COMMENT '是否当前设备:1-是, 0-否',
|
||||||
|
`last_active_at` DATETIME DEFAULT NULL COMMENT '最后活跃时间',
|
||||||
|
`login_at` DATETIME DEFAULT NULL COMMENT '登录时间',
|
||||||
|
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
UNIQUE KEY `uk_device_id` (`device_id`),
|
||||||
|
KEY `idx_user_id` (`user_id`),
|
||||||
|
KEY `idx_token` (`token`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户登录设备记录表';
|
||||||
Reference in New Issue
Block a user