Compare commits
1 Commits
6b7f6947db
...
feature-Da
| Author | SHA1 | Date | |
|---|---|---|---|
| 34392f137f |
@@ -1,482 +0,0 @@
|
||||
# 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年
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
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;
|
||||
|
||||
}
|
||||
@@ -34,8 +34,8 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
"/user/emailLogin",
|
||||
"/api/verification/send-email-code",
|
||||
"/ip/location",
|
||||
"/user/sendCode",
|
||||
"/download/{postId}"
|
||||
"/user/login",
|
||||
"/user/sendCode"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package com.bao.dating.context;
|
||||
|
||||
/**
|
||||
* 用户上下文类,用于保存当前线程的用户ID和token
|
||||
* 用户上下文类,用于保存当前线程的用户ID
|
||||
* @author lenovo
|
||||
*/
|
||||
public class UserContext {
|
||||
|
||||
private static final ThreadLocal<Long> USER_HOLDER = new ThreadLocal<>();
|
||||
private static final ThreadLocal<String> TOKEN_HOLDER = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 设置当前线程的用户ID
|
||||
@@ -26,26 +25,9 @@ public class UserContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前线程的token
|
||||
* @param token 用户token
|
||||
*/
|
||||
public static void setToken(String token) {
|
||||
TOKEN_HOLDER.set(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前线程的token
|
||||
* @return 当前token,如果未设置则返回null
|
||||
*/
|
||||
public static String getToken() {
|
||||
return TOKEN_HOLDER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前线程的用户ID和token
|
||||
* 清除当前线程的用户ID
|
||||
*/
|
||||
public static void clear() {
|
||||
USER_HOLDER.remove();
|
||||
TOKEN_HOLDER.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.bao.dating.controller;
|
||||
|
||||
import com.bao.dating.common.Result;
|
||||
import com.bao.dating.common.ResultCode;
|
||||
import com.bao.dating.pojo.dto.UserBanDTO;
|
||||
import com.bao.dating.pojo.entity.UserBan;
|
||||
import com.bao.dating.service.UserBanService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 管理员控制器
|
||||
* @author lenovo
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/admin")
|
||||
public class AdminController {
|
||||
@Autowired
|
||||
private UserBanService userBanService;
|
||||
|
||||
/**
|
||||
* 封禁用户
|
||||
*/
|
||||
@PostMapping("/{userId}/ban")
|
||||
public Result<?> banUser(@PathVariable Long userId,
|
||||
@RequestBody UserBanDTO userBanDTO) {
|
||||
userBanDTO.setUserId(userId);
|
||||
userBanService.banUser(userBanDTO);
|
||||
return Result.success(ResultCode.SUCCESS, "封禁成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 解封用户
|
||||
*/
|
||||
@PostMapping("/{userId}/unban")
|
||||
public Result<?> unbanUser(@PathVariable Long userId) {
|
||||
|
||||
userBanService.unbanUser(userId);
|
||||
return Result.success(ResultCode.SUCCESS, "解封成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询封禁状态
|
||||
*/
|
||||
@GetMapping("/{userId}/banInfo")
|
||||
public Result<UserBan> banInfo(@PathVariable Long userId) {
|
||||
|
||||
UserBan ban = userBanService.getActiveBan(userId);
|
||||
return Result.success(ResultCode.SUCCESS, "查询成功", ban);
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,6 @@ package com.bao.dating.controller;
|
||||
import com.bao.dating.context.UserContext;
|
||||
import com.bao.dating.service.ContactsService;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -14,7 +12,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/contacts")
|
||||
public class ContactsController {
|
||||
@Resource
|
||||
private ContactsService contactsService;
|
||||
@@ -45,56 +42,4 @@ public class ContactsController {
|
||||
result.put("data", friends);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.bao.dating.controller;
|
||||
|
||||
import com.bao.dating.common.Result;
|
||||
import com.bao.dating.common.ResultCode;
|
||||
import com.bao.dating.pojo.vo.DataAnalysisVO;
|
||||
import com.bao.dating.util.DataAnalysisRedisService;
|
||||
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 javax.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/dataAnalysis")
|
||||
public class DataAnalysisController {
|
||||
@Resource
|
||||
private DataAnalysisRedisService dataAnalysisRedisService;
|
||||
|
||||
/**
|
||||
* 统计动态
|
||||
* @param days 统计时常
|
||||
* @return result
|
||||
*/
|
||||
@GetMapping("/postTotal")
|
||||
public Result<DataAnalysisVO> postDataAnalysis(@RequestParam Integer days){
|
||||
if (checkParam(days)){
|
||||
return Result.error(ResultCode.PARAM_ERROR);
|
||||
}
|
||||
Long count = dataAnalysisRedisService.countPostTotalByDays(days);
|
||||
String timePeriod = dataAnalysisRedisService.calculateTimeRanges(LocalDateTime.now(), days);
|
||||
DataAnalysisVO dataAnalysisVO = new DataAnalysisVO();
|
||||
dataAnalysisVO.setTotal(count);
|
||||
dataAnalysisVO.setTimePeriod(timePeriod);
|
||||
return Result.success(ResultCode.SUCCESS,dataAnalysisVO);
|
||||
}
|
||||
|
||||
@GetMapping("/loginUserTotal")
|
||||
public Result<DataAnalysisVO> loginUserDataAnalysis(@RequestParam Integer days){
|
||||
if (checkParam(days)){
|
||||
return Result.error(ResultCode.PARAM_ERROR);
|
||||
}
|
||||
Long count = dataAnalysisRedisService.countLoginUserTotalByDays(days);
|
||||
String timePeriod = dataAnalysisRedisService.calculateTimeRanges(LocalDateTime.now(), days);
|
||||
DataAnalysisVO dataAnalysisVO = new DataAnalysisVO();
|
||||
dataAnalysisVO.setTotal(count);
|
||||
dataAnalysisVO.setTimePeriod(timePeriod);
|
||||
return Result.success(ResultCode.SUCCESS,dataAnalysisVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检验参数
|
||||
* @param days 查询天数
|
||||
* @return 参数是否合法
|
||||
*/
|
||||
public Boolean checkParam(Integer days){
|
||||
ArrayList<Integer> day = new ArrayList<>();
|
||||
day.add(7);
|
||||
day.add(15);
|
||||
day.add(30);
|
||||
day.add(365);
|
||||
return days != null && day.contains(days);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,17 @@
|
||||
package com.bao.dating.controller;
|
||||
|
||||
|
||||
import com.bao.dating.anno.Log;
|
||||
import com.bao.dating.common.Result;
|
||||
import com.bao.dating.common.ResultCode;
|
||||
import com.bao.dating.pojo.dto.PostRequestDTO;
|
||||
import com.bao.dating.pojo.entity.Post;
|
||||
import com.bao.dating.pojo.vo.PostEditVO;
|
||||
import com.bao.dating.service.PostService;
|
||||
import com.bao.dating.util.DataAnalysisRedisService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.awt.image.BufferedImage;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -29,6 +26,9 @@ public class PostController {
|
||||
@Autowired
|
||||
private PostService postService;
|
||||
|
||||
@Resource
|
||||
private DataAnalysisRedisService dataAnalysisRedisService;
|
||||
|
||||
/**
|
||||
* 上传媒体文件接口 like
|
||||
* @param files 媒体文件数组
|
||||
@@ -49,6 +49,9 @@ public class PostController {
|
||||
public Result<Post> createPostJson(@RequestBody PostRequestDTO postDTO) {
|
||||
// 调用 Service 层处理发布动态业务逻辑
|
||||
Post result = postService.createPost(postDTO);
|
||||
//这里将发布动态操作记录到redis中
|
||||
Long postId = result.getPostId();
|
||||
dataAnalysisRedisService.recordPostData(postId);
|
||||
return Result.success(ResultCode.SUCCESS, "动态发布成功,等待审核。", result);
|
||||
}
|
||||
|
||||
@@ -86,20 +89,4 @@ public class PostController {
|
||||
PostEditVO result = postService.updatePost(postId, postRequestDTO);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,21 +4,18 @@ import com.bao.dating.anno.Log;
|
||||
import com.bao.dating.common.Result;
|
||||
import com.bao.dating.common.ResultCode;
|
||||
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.UserLoginDTO;
|
||||
import com.bao.dating.pojo.dto.UserLoginWithDeviceDTO;
|
||||
import com.bao.dating.pojo.entity.User;
|
||||
import com.bao.dating.pojo.vo.UserDeviceVO;
|
||||
import com.bao.dating.pojo.vo.UserInfoVO;
|
||||
import com.bao.dating.pojo.vo.UserLoginVO;
|
||||
import com.bao.dating.service.UserDeviceService;
|
||||
import com.bao.dating.service.UserService;
|
||||
import com.bao.dating.util.DataAnalysisRedisService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -34,47 +31,23 @@ public class UserController {
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private UserDeviceService userDeviceService;
|
||||
@Resource
|
||||
private DataAnalysisRedisService dataAnalysisRedisService;
|
||||
|
||||
/**
|
||||
* 登录(带设备信息,推荐)
|
||||
* @param loginDTO 登录参数(包含设备信息)
|
||||
* @param request 请求
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public Result<UserLoginVO> login(@RequestBody UserLoginWithDeviceDTO loginDTO, HttpServletRequest request) {
|
||||
UserLoginDTO userLoginDTO = new UserLoginDTO();
|
||||
userLoginDTO.setUsername(loginDTO.getUsername());
|
||||
userLoginDTO.setPassword(loginDTO.getPassword());
|
||||
|
||||
UserLoginVO userLoginVO = userService.userLogin(userLoginDTO);
|
||||
|
||||
// 记录设备信息
|
||||
if (loginDTO.getDeviceId() != null && !loginDTO.getDeviceId().isEmpty()) {
|
||||
UserDeviceDTO deviceDTO = new UserDeviceDTO();
|
||||
deviceDTO.setDeviceId(loginDTO.getDeviceId());
|
||||
deviceDTO.setDeviceType(loginDTO.getDeviceType());
|
||||
deviceDTO.setDeviceName(loginDTO.getDeviceName());
|
||||
deviceDTO.setDeviceBrand(loginDTO.getDeviceBrand());
|
||||
deviceDTO.setOsVersion(loginDTO.getOsVersion());
|
||||
deviceDTO.setAppVersion(loginDTO.getAppVersion());
|
||||
deviceDTO.setIpAddress(getClientIp(request));
|
||||
|
||||
userDeviceService.recordDevice(userLoginVO.getUserId(), deviceDTO, userLoginVO.getToken());
|
||||
}
|
||||
|
||||
return Result.success(ResultCode.SUCCESS, "登录成功", userLoginVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单登录(不记录设备信息)
|
||||
* 登录
|
||||
* @param userLoginDTO 登录参数
|
||||
*/
|
||||
@PostMapping("/simple-login")
|
||||
public Result<UserLoginVO> simpleLogin(@RequestBody UserLoginDTO userLoginDTO) {
|
||||
UserLoginVO userLoginVO = userService.userLogin(userLoginDTO);
|
||||
return Result.success(ResultCode.SUCCESS, "登录成功", userLoginVO);
|
||||
@PostMapping("/login")
|
||||
public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO) {
|
||||
UserLoginVO userloginVO = userService.userLogin(userLoginDTO);
|
||||
//将用户登录记录到redis中
|
||||
if (userloginVO != null){
|
||||
Long userId = userloginVO.getUserId();
|
||||
dataAnalysisRedisService.recordLoginUserData(userId);
|
||||
}
|
||||
|
||||
return Result.success(ResultCode.SUCCESS, "登录成功", userloginVO);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,6 +139,11 @@ public class UserController {
|
||||
}
|
||||
//登录
|
||||
UserLoginVO vo = userService.loginByPhone(phone);
|
||||
//将用户登录操作记录到redis中
|
||||
if (vo != null){
|
||||
Long userId = vo.getUserId();
|
||||
dataAnalysisRedisService.recordLoginUserData(userId);
|
||||
}
|
||||
return Result.success(ResultCode.SUCCESS, "登录成功", vo);
|
||||
}
|
||||
|
||||
@@ -238,121 +216,10 @@ public class UserController {
|
||||
if (userLoginVO == null){
|
||||
return Result.error(ResultCode.FAIL,"请先注册用户或添加邮箱");
|
||||
}
|
||||
//将用户登录操作记录到redis中
|
||||
Long userId = userLoginVO.getUserId();
|
||||
dataAnalysisRedisService.recordLoginUserData(userId);
|
||||
return Result.success(ResultCode.SUCCESS,"用户登录成功",userLoginVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户是否在线
|
||||
* @param userId 用户ID
|
||||
* @return 用户是否在线
|
||||
*/
|
||||
@GetMapping("/{userId}/online")
|
||||
public Result<Boolean> isUserOnline(@PathVariable Long userId) {
|
||||
|
||||
boolean online = userService.isUserOnline(userId);
|
||||
return Result.success(ResultCode.SUCCESS, "查询成功", online);
|
||||
}
|
||||
|
||||
// ==================== 设备管理接口 ====================
|
||||
|
||||
/**
|
||||
* 获取用户所有登录设备
|
||||
* @return 设备列表
|
||||
*/
|
||||
@GetMapping("/devices")
|
||||
public Result<List<UserDeviceVO>> getUserDevices() {
|
||||
Long userId = UserContext.getUserId();
|
||||
List<UserDeviceVO> devices = userDeviceService.getUserDevices(userId);
|
||||
return Result.success(ResultCode.SUCCESS, "获取成功", devices);
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制下线某设备
|
||||
* @param deviceId 设备ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/devices/kick/{deviceId}")
|
||||
public Result<Void> kickDevice(@PathVariable String deviceId) {
|
||||
Long userId = UserContext.getUserId();
|
||||
boolean success = userDeviceService.kickDevice(userId, deviceId);
|
||||
if (success) {
|
||||
return Result.success(ResultCode.SUCCESS, "设备已下线", null);
|
||||
} else {
|
||||
return Result.error(ResultCode.FAIL, "设备不存在或无法下线");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下线其他所有设备(除当前设备外)
|
||||
* @return 被下线的设备数量
|
||||
*/
|
||||
@PostMapping("/devices/kick-others")
|
||||
public Result<Integer> kickOtherDevices() {
|
||||
Long userId = UserContext.getUserId();
|
||||
// 获取当前设备
|
||||
UserDeviceVO currentDevice = userDeviceService.getDeviceByToken(
|
||||
UserContext.getToken() != null ? UserContext.getToken() : ""
|
||||
);
|
||||
|
||||
String currentDeviceId = currentDevice != null ? currentDevice.getDeviceId() : null;
|
||||
if (currentDeviceId == null) {
|
||||
// 如果找不到当前设备,查询最新的设备
|
||||
List<UserDeviceVO> devices = userDeviceService.getUserDevices(userId);
|
||||
if (!devices.isEmpty()) {
|
||||
currentDeviceId = devices.get(0).getDeviceId();
|
||||
}
|
||||
}
|
||||
|
||||
if (currentDeviceId == null) {
|
||||
return Result.error(ResultCode.FAIL, "未找到设备");
|
||||
}
|
||||
|
||||
int count = userDeviceService.kickOtherDevices(userId, currentDeviceId);
|
||||
return Result.success(ResultCode.SUCCESS, "已下线 " + count + " 个设备", count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前设备信息
|
||||
* @return 当前设备信息
|
||||
*/
|
||||
@GetMapping("/devices/current")
|
||||
public Result<UserDeviceVO> getCurrentDevice() {
|
||||
String token = UserContext.getToken();
|
||||
if (token == null) {
|
||||
return Result.error(ResultCode.FAIL, "未登录");
|
||||
}
|
||||
UserDeviceVO device = userDeviceService.getDeviceByToken(token);
|
||||
return Result.success(ResultCode.SUCCESS, "获取成功", device);
|
||||
}
|
||||
|
||||
// ==================== 工具方法 ====================
|
||||
|
||||
/**
|
||||
* 获取客户端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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
package com.bao.dating.controller;
|
||||
|
||||
import com.bao.dating.common.Result;
|
||||
import com.bao.dating.common.ResultCode;
|
||||
import com.bao.dating.context.UserContext;
|
||||
import com.bao.dating.pojo.dto.UserDeviceDTO;
|
||||
import com.bao.dating.pojo.vo.UserDeviceVO;
|
||||
import com.bao.dating.service.UserDeviceService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户设备管理控制器
|
||||
* @author KilLze
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/user/device")
|
||||
public class UserDeviceController {
|
||||
|
||||
@Autowired
|
||||
private UserDeviceService userDeviceService;
|
||||
|
||||
/**
|
||||
* 获取用户所有登录设备
|
||||
* @return 设备列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public Result<List<UserDeviceVO>> getUserDevices() {
|
||||
Long userId = UserContext.getUserId();
|
||||
List<UserDeviceVO> devices = userDeviceService.getUserDevices(userId);
|
||||
return Result.success(ResultCode.SUCCESS, "获取成功", devices);
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制下线某设备
|
||||
* @param deviceId 设备ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/kick/{deviceId}")
|
||||
public Result<Void> kickDevice(@PathVariable String deviceId) {
|
||||
Long userId = UserContext.getUserId();
|
||||
boolean success = userDeviceService.kickDevice(userId, deviceId);
|
||||
if (success) {
|
||||
return Result.success(ResultCode.SUCCESS, "设备已下线", null);
|
||||
} else {
|
||||
return Result.error(ResultCode.FAIL, "设备不存在或无法下线");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前设备
|
||||
* @param deviceId 设备ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/current/{deviceId}")
|
||||
public Result<Void> setCurrentDevice(@PathVariable String deviceId) {
|
||||
Long userId = UserContext.getUserId();
|
||||
boolean success = userDeviceService.setCurrentDevice(userId, deviceId);
|
||||
if (success) {
|
||||
return Result.success(ResultCode.SUCCESS, "设置成功", null);
|
||||
} else {
|
||||
return Result.error(ResultCode.FAIL, "设备不存在");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除设备记录(不能删除当前设备)
|
||||
* @param deviceId 设备ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@DeleteMapping("/{deviceId}")
|
||||
public Result<Void> deleteDevice(@PathVariable String deviceId) {
|
||||
Long userId = UserContext.getUserId();
|
||||
boolean success = userDeviceService.deleteDevice(userId, deviceId);
|
||||
if (success) {
|
||||
return Result.success(ResultCode.SUCCESS, "删除成功", null);
|
||||
} else {
|
||||
return Result.error(ResultCode.FAIL, "设备不存在或无法删除当前设备");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下线其他所有设备(除当前设备外)
|
||||
* @return 被下线的设备数量
|
||||
*/
|
||||
@PostMapping("/kick-others")
|
||||
public Result<Integer> kickOtherDevices() {
|
||||
Long userId = UserContext.getUserId();
|
||||
// 获取当前设备ID
|
||||
List<UserDeviceVO> devices = userDeviceService.getUserDevices(userId);
|
||||
String currentDeviceId = null;
|
||||
for (UserDeviceVO device : devices) {
|
||||
if (Boolean.TRUE.equals(device.getIsCurrent())) {
|
||||
currentDeviceId = device.getDeviceId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentDeviceId == null) {
|
||||
return Result.error(ResultCode.FAIL, "未找到当前设备");
|
||||
}
|
||||
|
||||
int count = userDeviceService.kickOtherDevices(userId, currentDeviceId);
|
||||
return Result.success(ResultCode.SUCCESS, "已下线 " + count + " 个设备", count);
|
||||
}
|
||||
}
|
||||
@@ -70,18 +70,6 @@ public class TokenInterceptor implements HandlerInterceptor {
|
||||
// 解析 token
|
||||
Long userId = Long.valueOf(JwtUtil.getSubjectFromToken(token));
|
||||
|
||||
// 检查用户是否被封禁
|
||||
String banKey = "user:ban:" + userId;
|
||||
if (Boolean.TRUE.equals(redisTemplate.hasKey(banKey))) {
|
||||
String reason = String.valueOf(redisTemplate.opsForValue().get(banKey));
|
||||
log.error("用户 {} 已被封禁,原因:{}", userId, reason);
|
||||
|
||||
response.setStatus(403);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("账号已被封禁:" + reason);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从Redis获取存储的token进行比对
|
||||
Object redisTokenObj = redisTemplate.opsForValue().get("login:token:" + userId);
|
||||
String redisToken = redisTokenObj != null ? redisTokenObj.toString() : null;
|
||||
@@ -98,8 +86,6 @@ public class TokenInterceptor implements HandlerInterceptor {
|
||||
log.info("用户: {}", userId);
|
||||
// 保存 userId 到 ThreadLocal
|
||||
UserContext.setUserId(userId);
|
||||
// 保存 token 到 ThreadLocal
|
||||
UserContext.setToken(token);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Token 校验失败: {}", e.getMessage());
|
||||
|
||||
@@ -73,14 +73,6 @@ public class WsAuthInterceptor implements HandshakeInterceptor {
|
||||
|
||||
Long userId = Long.valueOf(userIdStr);
|
||||
|
||||
// 检查用户是否被封禁
|
||||
String banKey = "user:ban:" + userId;
|
||||
if (Boolean.TRUE.equals(redisTemplate.hasKey(banKey))) {
|
||||
String reason = String.valueOf(redisTemplate.opsForValue().get(banKey));
|
||||
log.error("WebSocket拒绝:用户 {} 被封禁,原因:{}", userId, reason);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从Redis获取存储的token进行比对
|
||||
String redisTokenKey = "login:token:" + userId;
|
||||
Object redisTokenObj = redisTemplate.opsForValue().get(redisTokenKey);
|
||||
|
||||
@@ -17,20 +17,4 @@ public interface ContactMapper {
|
||||
*/
|
||||
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,7 +5,6 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态Mapper
|
||||
@@ -118,12 +117,4 @@ public interface PostMapper {
|
||||
* @return 影响行数
|
||||
*/
|
||||
int decreaseFavoriteCount(Long postId);
|
||||
|
||||
/**
|
||||
* 根据动态id查询用户名和媒体信息
|
||||
*
|
||||
* @param postId 动态id
|
||||
* @return 用户名和媒体信息
|
||||
*/
|
||||
Map<String, Object> getUsernameByUserId(Long postId);
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.bao.dating.mapper;
|
||||
|
||||
import com.bao.dating.pojo.entity.UserBan;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface UserBanMapper {
|
||||
|
||||
/**
|
||||
* 新增封禁记录
|
||||
* @param userBan 封禁记录
|
||||
* @return 影响行数
|
||||
*/
|
||||
int insertBan(UserBan userBan);
|
||||
|
||||
/**
|
||||
* 查询是否存在生效中的封禁
|
||||
* @param userId 用户ID
|
||||
* @return 存在返回1,不存在返回0
|
||||
*/
|
||||
int existsActiveBan(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 查询生效中的封禁记录
|
||||
* @param userId 用户ID
|
||||
* @return 封禁记录
|
||||
*/
|
||||
UserBan selectActiveBan(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 解封用户
|
||||
* @param userId 用户ID
|
||||
* @return 影响行数
|
||||
*/
|
||||
int unbanUser(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 定时任务:过期自动解封
|
||||
* @return 影响行数
|
||||
*/
|
||||
int updateExpiredBans();
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.bao.dating.mapper;
|
||||
|
||||
import com.bao.dating.pojo.entity.UserDevice;
|
||||
import com.bao.dating.pojo.vo.UserDeviceVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户设备Mapper
|
||||
* @author KilLze
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserDeviceMapper {
|
||||
|
||||
int insert(UserDevice userDevice);
|
||||
|
||||
UserDevice selectByDeviceId(@Param("deviceId") String deviceId);
|
||||
|
||||
List<UserDeviceVO> selectByUserId(@Param("userId") Long userId);
|
||||
|
||||
int updateStatus(@Param("deviceId") String deviceId, @Param("status") Integer status);
|
||||
|
||||
int updateLastActiveAt(@Param("deviceId") String deviceId);
|
||||
|
||||
int clearCurrentDevice(@Param("userId") Long userId);
|
||||
|
||||
int setCurrentDevice(@Param("deviceId") String deviceId);
|
||||
|
||||
int deleteByDeviceId(@Param("deviceId") String deviceId);
|
||||
|
||||
UserDevice selectByToken(@Param("token") String token);
|
||||
|
||||
int update(UserDevice userDevice);
|
||||
|
||||
int deleteByUserId(@Param("userId") Long userId);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.bao.dating.pojo.dto;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 用户封禁数据传输对象
|
||||
* @author KilLze
|
||||
*/
|
||||
@Data
|
||||
public class UserBanDTO {
|
||||
private Long userId;
|
||||
private String reason;
|
||||
private Integer banDays;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.bao.dating.pojo.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户封禁记录
|
||||
* @author KilLze
|
||||
*/
|
||||
@Data
|
||||
public class UserBan {
|
||||
|
||||
private Long id;
|
||||
|
||||
private Long userId;
|
||||
|
||||
private String reason;
|
||||
|
||||
private LocalDateTime banStartTime;
|
||||
|
||||
private LocalDateTime banEndTime;
|
||||
|
||||
/**
|
||||
* 1:封禁中 0:已解封
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -29,6 +29,4 @@ public class ChatSessionsVO {
|
||||
private Integer topStatus;
|
||||
/** 免打扰状态 */
|
||||
private Integer muteStatus;
|
||||
/** 会话状态 */
|
||||
private Boolean online;
|
||||
}
|
||||
|
||||
11
src/main/java/com/bao/dating/pojo/vo/DataAnalysisVO.java
Normal file
11
src/main/java/com/bao/dating/pojo/vo/DataAnalysisVO.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package com.bao.dating.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DataAnalysisVO {
|
||||
//所有总数
|
||||
private Long total;
|
||||
//时间段
|
||||
private String timePeriod;
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -26,5 +26,4 @@ public class UserInfoVO implements Serializable {
|
||||
private LocalDateTime createdAt;
|
||||
private Double latitude;
|
||||
private Double longitude;
|
||||
private Boolean online;
|
||||
}
|
||||
|
||||
@@ -15,20 +15,6 @@ public interface ContactsService {
|
||||
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,7 +5,6 @@ import com.bao.dating.pojo.entity.Post;
|
||||
import com.bao.dating.pojo.vo.PostEditVO;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -56,11 +55,4 @@ public interface PostService {
|
||||
* @return 用户id
|
||||
*/
|
||||
Long selectUserIdByPostId(Long postId);
|
||||
|
||||
/**
|
||||
* 下载动态图片并添加水印
|
||||
* @param postId 动态ID
|
||||
* @return 带水印的图片
|
||||
*/
|
||||
BufferedImage downloadWithWatermark(Long postId) throws Exception;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.bao.dating.service;
|
||||
|
||||
import com.bao.dating.pojo.dto.UserBanDTO;
|
||||
import com.bao.dating.pojo.entity.UserBan;
|
||||
|
||||
/**
|
||||
* 用户封禁服务接口
|
||||
* @author KilLze
|
||||
*/
|
||||
public interface UserBanService {
|
||||
|
||||
/**
|
||||
* 封禁用户
|
||||
* @param userBanDTO 用户封禁信息
|
||||
*
|
||||
*/
|
||||
void banUser(UserBanDTO userBanDTO);
|
||||
|
||||
/**
|
||||
* 解封用户
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
void unbanUser(Long userId);
|
||||
|
||||
/**
|
||||
* 查询封禁信息
|
||||
* @param userId 用户ID
|
||||
* @return 封禁信息
|
||||
*/
|
||||
UserBan getActiveBan(Long userId);
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.bao.dating.service;
|
||||
|
||||
import com.bao.dating.pojo.dto.UserDeviceDTO;
|
||||
import com.bao.dating.pojo.vo.UserDeviceVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户设备服务接口
|
||||
* @author KilLze
|
||||
*/
|
||||
public interface UserDeviceService {
|
||||
|
||||
/**
|
||||
* 记录用户登录设备
|
||||
* @param userId 用户ID
|
||||
* @param deviceDTO 设备信息
|
||||
* @param token 登录Token
|
||||
* @return 设备信息
|
||||
*/
|
||||
UserDeviceVO recordDevice(Long userId, UserDeviceDTO deviceDTO, String token);
|
||||
|
||||
/**
|
||||
* 获取用户所有登录设备
|
||||
* @param userId 用户ID
|
||||
* @return 设备列表
|
||||
*/
|
||||
List<UserDeviceVO> getUserDevices(Long userId);
|
||||
|
||||
/**
|
||||
* 强制下线某设备
|
||||
* @param userId 用户ID
|
||||
* @param deviceId 设备ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean kickDevice(Long userId, String deviceId);
|
||||
|
||||
/**
|
||||
* 设置某设备为当前设备
|
||||
* @param userId 用户ID
|
||||
* @param deviceId 设备ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean setCurrentDevice(Long userId, String deviceId);
|
||||
|
||||
/**
|
||||
* 删除设备记录
|
||||
* @param userId 用户ID
|
||||
* @param deviceId 设备ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean deleteDevice(Long userId, String deviceId);
|
||||
|
||||
/**
|
||||
* 更新设备活跃时间
|
||||
* @param token 用户Token
|
||||
*/
|
||||
void updateDeviceActive(String token);
|
||||
|
||||
/**
|
||||
* 根据Token获取设备信息
|
||||
* @param token 用户Token
|
||||
* @return 设备信息
|
||||
*/
|
||||
UserDeviceVO getDeviceByToken(String token);
|
||||
|
||||
/**
|
||||
* 下线其他所有设备(除当前设备外)
|
||||
* @param userId 用户ID
|
||||
* @param currentDeviceId 当前设备ID
|
||||
* @return 被下线的设备数量
|
||||
*/
|
||||
int kickOtherDevices(Long userId, String currentDeviceId);
|
||||
}
|
||||
@@ -92,11 +92,4 @@ public interface UserService {
|
||||
* @return 用户列表
|
||||
*/
|
||||
List<UserInfoVO> findNearbyUsers(double lat,double lng,double radiusKm);
|
||||
|
||||
/**
|
||||
* 判断用户是否在线
|
||||
* @param userId 用户ID
|
||||
* @return 是否在线
|
||||
*/
|
||||
boolean isUserOnline(Long userId);
|
||||
}
|
||||
|
||||
@@ -236,7 +236,6 @@ public class ChatServiceImpl implements ChatService {
|
||||
vo.setSessionName("用户" + session.getTargetUserId());
|
||||
vo.setAvatarUrl(null);
|
||||
}
|
||||
vo.setOnline(userService.isUserOnline(vo.getTargetUserId()));
|
||||
return vo;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.bao.dating.service.ContactsService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -20,34 +19,4 @@ public class ContactServiceImpl implements ContactsService {
|
||||
// 直接调用Mapper查询,无额外封装
|
||||
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,13 +10,11 @@ import com.bao.dating.mapper.PostLikeMapper;
|
||||
import com.bao.dating.mapper.PostMapper;
|
||||
import com.bao.dating.pojo.dto.PostRequestDTO;
|
||||
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.service.PostService;
|
||||
import com.bao.dating.common.aliyun.AliOssUtil;
|
||||
import com.bao.dating.service.UserService;
|
||||
import com.bao.dating.util.FileUtil;
|
||||
import com.bao.dating.util.WatermarkUtil;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -24,7 +22,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -60,9 +57,6 @@ public class PostServiceImpl implements PostService {
|
||||
@Autowired
|
||||
private CommentsMapper commentsMapper;
|
||||
|
||||
@Autowired
|
||||
private WatermarkUtil watermarkUtil;
|
||||
|
||||
/**
|
||||
* 上传媒体文件
|
||||
* @param files 媒体文件数组
|
||||
@@ -350,61 +344,4 @@ public class PostServiceImpl implements PostService {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.bao.dating.service.impl;
|
||||
|
||||
import com.bao.dating.mapper.UserBanMapper;
|
||||
import com.bao.dating.pojo.dto.UserBanDTO;
|
||||
import com.bao.dating.pojo.entity.UserBan;
|
||||
import com.bao.dating.service.UserBanService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
public class UserBanServiceImpl implements UserBanService {
|
||||
|
||||
@Autowired
|
||||
private UserBanMapper userBanMapper;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Override
|
||||
public void banUser(UserBanDTO userBanDTO) {
|
||||
|
||||
// 已被封禁,直接拒绝
|
||||
if (userBanMapper.existsActiveBan(userBanDTO.getUserId()) > 0) {
|
||||
throw new RuntimeException("用户已处于封禁状态");
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime endTime = userBanDTO.getBanDays() == null ? null : now.plusDays(userBanDTO.getBanDays());
|
||||
|
||||
// 1. 写数据库
|
||||
UserBan ban = new UserBan();
|
||||
ban.setUserId(userBanDTO.getUserId());
|
||||
ban.setReason(userBanDTO.getReason());
|
||||
ban.setBanStartTime(now);
|
||||
ban.setBanEndTime(endTime);
|
||||
ban.setStatus(1);
|
||||
userBanMapper.insertBan(ban);
|
||||
|
||||
// 2. 写 Redis
|
||||
String key = "user:ban:" + userBanDTO.getUserId();
|
||||
if (userBanDTO.getBanDays() == null) {
|
||||
redisTemplate.opsForValue().set(key, userBanDTO.getReason());
|
||||
} else {
|
||||
redisTemplate.opsForValue().set(key, userBanDTO.getReason(), userBanDTO.getBanDays(), TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
// 3. 踢下线
|
||||
redisTemplate.delete("login:token:" + userBanDTO.getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 解封用户
|
||||
*/
|
||||
@Override
|
||||
public void unbanUser(Long userId) {
|
||||
// 更新数据库
|
||||
userBanMapper.unbanUser(userId);
|
||||
|
||||
// 删除 Redis
|
||||
redisTemplate.delete("user:ban:" + userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户封禁信息
|
||||
*/
|
||||
@Override
|
||||
public UserBan getActiveBan(Long userId) {
|
||||
return userBanMapper.selectActiveBan(userId);
|
||||
}
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
package com.bao.dating.service.impl;
|
||||
|
||||
import com.bao.dating.context.UserContext;
|
||||
import com.bao.dating.mapper.UserDeviceMapper;
|
||||
import com.bao.dating.mapper.UserMapper;
|
||||
import com.bao.dating.pojo.dto.UserDeviceDTO;
|
||||
import com.bao.dating.pojo.entity.User;
|
||||
import com.bao.dating.pojo.entity.UserDevice;
|
||||
import com.bao.dating.pojo.vo.UserDeviceVO;
|
||||
import com.bao.dating.service.UserDeviceService;
|
||||
import com.bao.dating.session.WsSessionManager;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户设备服务实现类
|
||||
* @author KilLze
|
||||
*/
|
||||
@Service
|
||||
public class UserDeviceServiceImpl implements UserDeviceService {
|
||||
|
||||
@Autowired
|
||||
private UserDeviceMapper userDeviceMapper;
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Autowired
|
||||
private WsSessionManager wsSessionManager;
|
||||
|
||||
@Override
|
||||
public UserDeviceVO recordDevice(Long userId, UserDeviceDTO deviceDTO, String token) {
|
||||
// 先查找是否已存在该设备
|
||||
UserDevice existingDevice = userDeviceMapper.selectByDeviceId(deviceDTO.getDeviceId());
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
if (existingDevice != null) {
|
||||
// 设备已存在,更新信息
|
||||
existingDevice.setToken(token);
|
||||
existingDevice.setStatus(1); // 在线
|
||||
existingDevice.setIsCurrent(1); // 设为当前设备
|
||||
existingDevice.setLastActiveAt(now);
|
||||
existingDevice.setIpAddress(deviceDTO.getIpAddress());
|
||||
existingDevice.setLocation(deviceDTO.getLocation());
|
||||
userDeviceMapper.update(existingDevice);
|
||||
|
||||
// 清除该用户其他设备的当前标记
|
||||
userDeviceMapper.clearCurrentDevice(userId);
|
||||
userDeviceMapper.setCurrentDevice(deviceDTO.getDeviceId());
|
||||
|
||||
UserDeviceVO vo = new UserDeviceVO();
|
||||
BeanUtils.copyProperties(existingDevice, vo);
|
||||
vo.setDeviceType(getDeviceTypeName(existingDevice.getDeviceType()));
|
||||
vo.setIsCurrent(true);
|
||||
vo.setIsOnline(true);
|
||||
return vo;
|
||||
}
|
||||
|
||||
// 新设备,插入记录
|
||||
UserDevice userDevice = new UserDevice();
|
||||
userDevice.setUserId(userId);
|
||||
userDevice.setDeviceId(deviceDTO.getDeviceId());
|
||||
userDevice.setDeviceType(deviceDTO.getDeviceType());
|
||||
userDevice.setDeviceName(deviceDTO.getDeviceName());
|
||||
userDevice.setDeviceBrand(deviceDTO.getDeviceBrand());
|
||||
userDevice.setOsVersion(deviceDTO.getOsVersion());
|
||||
userDevice.setAppVersion(deviceDTO.getAppVersion());
|
||||
userDevice.setIpAddress(deviceDTO.getIpAddress());
|
||||
userDevice.setLocation(deviceDTO.getLocation());
|
||||
userDevice.setToken(token);
|
||||
userDevice.setStatus(1); // 在线
|
||||
userDevice.setIsCurrent(1); // 设为当前设备
|
||||
userDevice.setLastActiveAt(now);
|
||||
userDevice.setLoginAt(now);
|
||||
|
||||
// 清除该用户其他设备的当前标记
|
||||
userDeviceMapper.clearCurrentDevice(userId);
|
||||
|
||||
userDeviceMapper.insert(userDevice);
|
||||
|
||||
UserDeviceVO vo = new UserDeviceVO();
|
||||
BeanUtils.copyProperties(userDevice, vo);
|
||||
vo.setDeviceType(getDeviceTypeName(userDevice.getDeviceType()));
|
||||
vo.setIsCurrent(true);
|
||||
vo.setIsOnline(true);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserDeviceVO> getUserDevices(Long userId) {
|
||||
List<UserDeviceVO> devices = userDeviceMapper.selectByUserId(userId);
|
||||
// 格式化设备类型
|
||||
for (UserDeviceVO device : devices) {
|
||||
device.setDeviceType(getDeviceTypeName(device.getDeviceType()));
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean kickDevice(Long userId, String deviceId) {
|
||||
UserDevice device = userDeviceMapper.selectByDeviceId(deviceId);
|
||||
|
||||
// 验证设备属于该用户
|
||||
if (device == null || !device.getUserId().equals(userId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 删除设备记录
|
||||
userDeviceMapper.deleteByDeviceId(deviceId);
|
||||
|
||||
// 从Redis中删除该设备的登录状态
|
||||
String redisKey = "login:token:" + userId;
|
||||
redisTemplate.delete(redisKey);
|
||||
|
||||
// 如果有WebSocket连接,关闭连接
|
||||
wsSessionManager.removeSession(device.getToken());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setCurrentDevice(Long userId, String deviceId) {
|
||||
UserDevice device = userDeviceMapper.selectByDeviceId(deviceId);
|
||||
|
||||
if (device == null || !device.getUserId().equals(userId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 清除所有当前设备标记
|
||||
userDeviceMapper.clearCurrentDevice(userId);
|
||||
|
||||
// 设置新的当前设备
|
||||
userDeviceMapper.setCurrentDevice(deviceId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteDevice(Long userId, String deviceId) {
|
||||
UserDevice device = userDeviceMapper.selectByDeviceId(deviceId);
|
||||
|
||||
if (device == null || !device.getUserId().equals(userId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 不能删除当前设备
|
||||
if (device.getIsCurrent() == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
userDeviceMapper.deleteByDeviceId(deviceId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDeviceActive(String token) {
|
||||
if (token != null && !token.isEmpty()) {
|
||||
userDeviceMapper.updateLastActiveAt(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDeviceVO getDeviceByToken(String token) {
|
||||
UserDevice device = userDeviceMapper.selectByToken(token);
|
||||
if (device == null) {
|
||||
return null;
|
||||
}
|
||||
UserDeviceVO vo = new UserDeviceVO();
|
||||
BeanUtils.copyProperties(device, vo);
|
||||
vo.setDeviceType(getDeviceTypeName(device.getDeviceType()));
|
||||
vo.setIsOnline(device.getStatus() == 1);
|
||||
vo.setIsCurrent(device.getIsCurrent() == 1);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int kickOtherDevices(Long userId, String currentDeviceId) {
|
||||
List<UserDeviceVO> devices = userDeviceMapper.selectByUserId(userId);
|
||||
int count = 0;
|
||||
|
||||
for (UserDeviceVO device : devices) {
|
||||
if (!device.getDeviceId().equals(currentDeviceId)) {
|
||||
// 踢掉设备
|
||||
userDeviceMapper.deleteByDeviceId(device.getDeviceId());
|
||||
|
||||
// 清除登录状态
|
||||
String redisKey = "login:token:" + userId;
|
||||
redisTemplate.delete(redisKey);
|
||||
|
||||
// 关闭WebSocket连接
|
||||
wsSessionManager.removeSession(device.getDeviceId());
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备类型名称
|
||||
*/
|
||||
private String getDeviceTypeName(Integer deviceType) {
|
||||
if (deviceType == null) {
|
||||
return "未知";
|
||||
}
|
||||
switch (deviceType) {
|
||||
case 1: return "Android";
|
||||
case 2: return "iOS";
|
||||
case 3: return "Web";
|
||||
case 4: return "其他";
|
||||
default: return "未知";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import com.bao.dating.util.CodeUtil;
|
||||
import com.bao.dating.util.FileUtil;
|
||||
import com.bao.dating.util.JwtUtil;
|
||||
import com.bao.dating.util.MD5Util;
|
||||
import com.bao.dating.util.UserBanUtil;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -67,9 +66,6 @@ public class UserServiceImpl implements UserService {
|
||||
@Autowired
|
||||
private VerificationCodeService verificationCodeService;
|
||||
|
||||
@Autowired
|
||||
private UserBanUtil userBanValidator;
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*
|
||||
@@ -96,8 +92,6 @@ public class UserServiceImpl implements UserService {
|
||||
if (!match) {
|
||||
throw new RuntimeException("密码错误");
|
||||
}
|
||||
// 用户封禁验证
|
||||
userBanValidator.validateUserNotBanned(user.getUserId());
|
||||
// 生成token
|
||||
String token = JwtUtil.generateToken(String.valueOf(user.getUserId()));
|
||||
|
||||
@@ -124,9 +118,6 @@ public class UserServiceImpl implements UserService {
|
||||
@Override
|
||||
public void logout(String token) {
|
||||
Claims claims = JwtUtil.getClaimsFromToken(token);
|
||||
// 获取token信息
|
||||
String subject = claims.getSubject();
|
||||
// 获取token的过期时间
|
||||
Date expiration = claims.getExpiration();
|
||||
// 判断 token 是否已过期
|
||||
long ttl = expiration.getTime() - System.currentTimeMillis();
|
||||
@@ -135,10 +126,6 @@ public class UserServiceImpl implements UserService {
|
||||
return;
|
||||
}
|
||||
|
||||
// 从Redis中删除登录token记录
|
||||
String loginTokenKey = "login:token:" + subject;
|
||||
redisTemplate.delete(loginTokenKey);
|
||||
|
||||
String logoutKey = "jwt:blacklist:" + token;
|
||||
redisTemplate.opsForValue().set(
|
||||
logoutKey,
|
||||
@@ -498,23 +485,4 @@ public class UserServiceImpl implements UserService {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserOnline(Long userId) {
|
||||
if (userId == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. 是否被封禁
|
||||
String banKey = "user:ban:" + userId;
|
||||
if (Boolean.TRUE.equals(redisTemplate.hasKey(banKey))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 是否存在登录 token
|
||||
String tokenKey = "login:token:" + userId;
|
||||
Boolean online = redisTemplate.hasKey(tokenKey);
|
||||
|
||||
return Boolean.TRUE.equals(online);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.bao.dating.task;
|
||||
|
||||
import com.bao.dating.mapper.UserBanMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@EnableScheduling
|
||||
public class UserBanScheduleTask {
|
||||
|
||||
@Autowired
|
||||
private UserBanMapper userBanMapper;
|
||||
|
||||
/**
|
||||
* 每天凌晨 3 点同步过期封禁
|
||||
*/
|
||||
@Scheduled(cron = "0 0 3 * * ?")
|
||||
public void syncExpiredUserBan() {
|
||||
int rows = userBanMapper.updateExpiredBans();
|
||||
log.info("封禁同步任务执行完成,解封 {} 个用户", rows);
|
||||
}
|
||||
}
|
||||
101
src/main/java/com/bao/dating/util/DataAnalysisRedisService.java
Normal file
101
src/main/java/com/bao/dating/util/DataAnalysisRedisService.java
Normal file
@@ -0,0 +1,101 @@
|
||||
package com.bao.dating.util;
|
||||
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 基于redis的数据分析工具 -统计动态总数和登录人数
|
||||
*/
|
||||
@Component
|
||||
public class DataAnalysisRedisService {
|
||||
// Redis Key 定义
|
||||
//动态总数
|
||||
private static final String REDIS_KEY_POST_TOTAL = "data:post:total";
|
||||
//活跃人数
|
||||
private static final String REDIS_KEY_LOGIN_USER_TOTAL = "data:loginUser:total";
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
/**
|
||||
* 记录动态向redis中添加数据
|
||||
* @param postId 动态id
|
||||
*/
|
||||
public void recordPostData(Long postId) {
|
||||
// 获取当前毫秒级时间戳(作为Sorted Set的score)
|
||||
long currentTimestamp = System.currentTimeMillis();
|
||||
// 1. 记录动态数据
|
||||
redisTemplate.opsForZSet().add(REDIS_KEY_POST_TOTAL, postId, currentTimestamp);
|
||||
// 可选:设置Key的过期时间(1年+1天,避免数据无限累积)
|
||||
setKeyExpire(REDIS_KEY_POST_TOTAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录动态向redis中添加数据
|
||||
* @param userId 用户id
|
||||
*/
|
||||
public void recordLoginUserData(Long userId) {
|
||||
// 获取当前毫秒级时间戳(作为Sorted Set的score)
|
||||
long currentTimestamp = System.currentTimeMillis();
|
||||
// 1. 记录动态数据
|
||||
redisTemplate.opsForZSet().add(REDIS_KEY_LOGIN_USER_TOTAL, userId, currentTimestamp);
|
||||
// 可选:设置Key的过期时间(1年+1天,避免数据无限累积)
|
||||
setKeyExpire(REDIS_KEY_LOGIN_USER_TOTAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Redis Key的过期时间(1年+1天,确保1年内的数据都能被查询)
|
||||
*/
|
||||
private void setKeyExpire(String redisKey) {
|
||||
if (Boolean.FALSE.equals(redisTemplate.hasKey(redisKey))) {
|
||||
redisTemplate.expire(redisKey, 366, TimeUnit.DAYS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计指定时间段内发布的动态
|
||||
* @param days 指定时间段
|
||||
* @return 总数
|
||||
*/
|
||||
public Long countPostTotalByDays(int days) {
|
||||
// 1. 计算时间范围:结束时间=当前时间,起始时间=当前时间 - N天的毫秒数
|
||||
long endTimestamp = System.currentTimeMillis();
|
||||
long startTimestamp = endTimestamp - (long) days * 24 * 60 * 60 * 1000;
|
||||
// 2. key min max - 统计指定score范围内的成员数量(高效,直接返回数量)
|
||||
return redisTemplate.opsForZSet().count(REDIS_KEY_POST_TOTAL, startTimestamp, endTimestamp);
|
||||
}
|
||||
|
||||
public Long countLoginUserTotalByDays(int days) {
|
||||
// 1. 计算时间范围:结束时间=当前时间,起始时间=当前时间 - N天的毫秒数
|
||||
long endTimestamp = System.currentTimeMillis();
|
||||
long startTimestamp = endTimestamp - (long) days * 24 * 60 * 60 * 1000;
|
||||
// 2. key min max - 统计指定score范围内的成员数量(高效,直接返回数量)
|
||||
return redisTemplate.opsForZSet().count(REDIS_KEY_LOGIN_USER_TOTAL, startTimestamp, endTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心方法:传入当前时间,返回各时间段的结果
|
||||
* @param currentDateTime 当前时间(LocalDateTime类型,可直接通过LocalDateTime.now()获取)
|
||||
*/
|
||||
public String calculateTimeRanges(LocalDateTime currentDateTime,int days) {
|
||||
// 1. 计算各时间段的起始时间(当前时间往前推对应时长)
|
||||
LocalDateTime sevenDaysAgo = currentDateTime.minusDays(7);
|
||||
LocalDateTime fifteenDaysAgo = currentDateTime.minusDays(15);
|
||||
LocalDateTime thirtyDaysAgo = currentDateTime.minusDays(30);
|
||||
LocalDateTime oneYearAgo = currentDateTime.minusYears(1);
|
||||
if (days == 7)
|
||||
return sevenDaysAgo + " - " + currentDateTime;
|
||||
if (days == 15)
|
||||
return fifteenDaysAgo + " - " + currentDateTime;
|
||||
if (days == 30)
|
||||
return thirtyDaysAgo + " - " + currentDateTime;
|
||||
if (days == 365)
|
||||
return oneYearAgo + " - " + currentDateTime;
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package com.bao.dating.util;
|
||||
|
||||
import com.bao.dating.context.UserContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 用户封禁验证工具类
|
||||
* 提供统一的用户封禁状态检查功能
|
||||
*
|
||||
* @author KilLze
|
||||
*/
|
||||
@Component
|
||||
public class UserBanUtil {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
/**
|
||||
* 验证指定用户是否被封禁
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @throws RuntimeException 如果用户被封禁则抛出异常
|
||||
*/
|
||||
public void validateUserNotBanned(Long userId) {
|
||||
String banKey = "user:ban:" + userId;
|
||||
if (Boolean.TRUE.equals(redisTemplate.hasKey(banKey))) {
|
||||
String reason = (String) redisTemplate.opsForValue().get(banKey);
|
||||
|
||||
// 获取剩余过期时间(秒)
|
||||
Long ttlSeconds = redisTemplate.getExpire(banKey, TimeUnit.SECONDS);
|
||||
String remainingTime = "";
|
||||
if (ttlSeconds != null && ttlSeconds > 0) {
|
||||
long days = ttlSeconds / (24 * 3600);
|
||||
long hours = (ttlSeconds % (24 * 3600)) / 3600;
|
||||
long minutes = (ttlSeconds % 3600) / 60;
|
||||
|
||||
if (days > 0) {
|
||||
remainingTime = ",剩余时间:" + days + "天" + hours + "小时";
|
||||
} else if (hours > 0) {
|
||||
remainingTime = ",剩余时间:" + hours + "小时" + minutes + "分钟";
|
||||
} else {
|
||||
remainingTime = ",剩余时间:" + minutes + "分钟";
|
||||
}
|
||||
} else {
|
||||
remainingTime = ",永久封禁";
|
||||
}
|
||||
|
||||
throw new RuntimeException("账号已被封禁,原因:" + reason + remainingTime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证当前登录用户是否被封禁
|
||||
*
|
||||
* @throws RuntimeException 如果用户被封禁则抛出异常
|
||||
*/
|
||||
public void validateCurrentUserNotBanned() {
|
||||
Long userId = UserContext.getUserId();
|
||||
validateUserNotBanned(userId);
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
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,29 +21,5 @@
|
||||
AND c.relation_type != 3 -- 排除黑名单
|
||||
AND c.user_id != c.contact_user_id -- 排除自己
|
||||
</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>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -135,11 +135,5 @@
|
||||
<select id="selectFavoriteCount" resultType="java.lang.Integer">
|
||||
select dating.post.favorite_count from dating.post where post.post_id = #{postId}
|
||||
</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>
|
||||
@@ -1,50 +0,0 @@
|
||||
<?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.UserBanMapper">
|
||||
|
||||
<!-- 向数据库中添加用户封禁信息 -->
|
||||
<insert id="insertBan" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO user_ban
|
||||
(user_id, reason, ban_start_time, ban_end_time, status)
|
||||
VALUES
|
||||
(#{userId}, #{reason}, #{banStartTime}, #{banEndTime}, #{status})
|
||||
</insert>
|
||||
|
||||
<!-- 查询指定用户是否存在未过期的封禁信息 -->
|
||||
<select id="existsActiveBan" resultType="int">
|
||||
SELECT COUNT(1)
|
||||
FROM user_ban
|
||||
WHERE user_id = #{userId}
|
||||
AND status = 1
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<!-- 查询指定用户是否存在未过期的封禁信息 -->
|
||||
<select id="selectActiveBan" resultType="com.bao.dating.pojo.entity.UserBan">
|
||||
SELECT *
|
||||
FROM user_ban
|
||||
WHERE user_id = #{userId}
|
||||
AND status = 1
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<!-- 解封指定用户 -->
|
||||
<update id="unbanUser">
|
||||
UPDATE user_ban
|
||||
SET status = 0
|
||||
WHERE user_id = #{userId}
|
||||
AND status = 1
|
||||
</update>
|
||||
|
||||
<!-- 批量更新已过期的封禁信息 -->
|
||||
<update id="updateExpiredBans">
|
||||
UPDATE user_ban
|
||||
SET status = 0
|
||||
WHERE status = 1
|
||||
AND ban_end_time IS NOT NULL
|
||||
AND ban_end_time < NOW()
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
@@ -1,86 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,22 +0,0 @@
|
||||
-- 用户设备登录记录表
|
||||
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