彩票助手后端1.0

This commit is contained in:
lihanqi
2025-08-01 19:09:57 +08:00
commit 93c8ace8e7
204 changed files with 18805 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

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

33
.gitignore vendored Normal file
View File

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

19
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.10/apache-maven-3.9.10-bin.zip

484
Excel导入使用说明.md Normal file
View File

@@ -0,0 +1,484 @@
# Excel数据导入功能使用说明
## 功能概述
本系统提供了Excel数据导入功能可以将包含T1、T2、T3、T4、T5、T6、T7、T8、T10和T11工作表的Excel文件数据导入到十四个数据库表中
### 红球数据T1 Sheet
- `history_all` - 红球全部历史数据表
- `history_100` - 红球最近100期数据表
- `history_top` - 红球历史数据排行表
- `history_top_100` - 红球100期数据排行表
### 蓝球数据T2 Sheet
- `blue_history_all` - 蓝球全部历史数据表
- `blue_history_100` - 蓝球最近100期数据表
- `blue_history_top` - 蓝球历史数据排行表
- `blue_history_top_100` - 蓝球100期数据排行表
### 红球线系数数据T3 Sheet
- `t3` - 红球组红球的线系数表
### 蓝球组红球线系数数据T4 Sheet
- `t4` - 蓝球组红球的线系数表
### 蓝球组蓝球线系数数据T5 Sheet
- `t5` - 蓝球组蓝球的线系数表
### 红球组蓝球线系数数据T6 Sheet
- `t6` - 红球组蓝球的线系数表
### 红球组红球面系数数据T7 Sheet
- `t7` - 红球组红球的面系数表
### 红球组蓝球面系数数据T8 Sheet
- `t8` - 红球组蓝球的面系数表
### 彩票开奖信息数据T10 Sheet
- `lottery_draws` - 彩票开奖信息表
### 蓝球组红球面系数数据T11 Sheet
- `t11` - 蓝球组红球的面系数表
## Excel文件格式要求
### 文件要求
- **文件格式**:必须是`.xlsx`格式
- **工作表名称**:必须包含名为`T1``T2``T3``T4``T5``T6``T7``T8``T10``T11`的工作表
- **编码**:支持中文
### 数据结构要求
**T1工作表红球数据**的数据结构如下:
| 列位置 | 列名 | 对应表 | 字段说明 |
|--------|------|--------|----------|
| A-G | 全部历史数据 | history_all | 球号、出现频次、出现频率%、平均隐现期次、最长隐现期次、最多连出期次、点系数 |
| H-M | 最近100期数据 | history_100 | 出现频次、出现频率%、平均隐现期、当前隐现期、最多连出期次、点系数 |
| N-P | 历史数据排行 | history_top | 排行、球号、点系数 |
| Q-S | 100期数据排行 | history_top_100 | 排行、球号、点系数 |
**T2工作表蓝球数据**的数据结构如下:
| 列位置 | 列名 | 对应表 | 字段说明 |
|--------|------|--------|----------|
| A-G | 全部历史数据 | blue_history_all | 球号、出现频次、出现频率%、平均隐现期次、最长隐现期次、最多连出期次、点系数 |
| H-M | 最近100期数据 | blue_history_100 | 出现频次、出现频率%、平均隐现期、当前隐现期、最多连出期次、点系数 |
| N-P | 历史数据排行 | blue_history_top | 排行、球号、点系数 |
| Q-S | 100期数据排行 | blue_history_top_100 | 排行、球号、点系数 |
**T3工作表红球线系数数据**的数据结构如下:
| 列位置 | 数据组织 | 对应表 | 字段说明 |
|--------|----------|--------|----------|
| C | 1号组线系数 | t3 | 主球=1从球=1-33线系数=C列 |
| F | 2号组线系数 | t3 | 主球=2从球=1-33线系数=F列 |
| I | 3号组线系数 | t3 | 主球=3从球=1-33线系数=I列 |
| ... | 依此类推 | t3 | 每三列为一组,组号即主球号 |
**说明**T3工作表每三列为一组数据每组有33行数据从球号固定为1-33行号线系数在C、F、I、L...列。
**T4工作表蓝球组红球线系数数据**的数据结构如下:
| 列位置 | 数据组织 | 对应表 | 字段说明 |
|--------|----------|--------|----------|
| C | 蓝球1号线系数 | t4 | 主球=1从球=1-33线系数=C列 |
| F | 蓝球2号线系数 | t4 | 主球=2从球=1-33线系数=F列 |
| I | 蓝球3号线系数 | t4 | 主球=3从球=1-33线系数=I列 |
| ... | 依此类推 | t4 | 每三列为一组最多16组 |
**说明**T4工作表每三列为一组数据每组有33行数据蓝球号码1-16主球红球号码1-33从球行号线系数在C、F、I、L...列。
**T5工作表蓝球组蓝球线系数数据**的数据结构如下:
| 列位置 | 数据组织 | 对应表 | 字段说明 |
|--------|----------|--------|----------|
| C | 蓝球1号线系数 | t5 | 主球=1从球=1-16线系数=C列 |
| F | 蓝球2号线系数 | t5 | 主球=2从球=1-16线系数=F列 |
| I | 蓝球3号线系数 | t5 | 主球=3从球=1-16线系数=I列 |
| ... | 依此类推 | t5 | 每三列为一组最多16组 |
**说明**T5工作表每三列为一组数据每组有16行数据蓝球号码1-16主球和从球线系数在C、F、I、L...列。
**T6工作表红球组蓝球线系数数据**的数据结构如下:
| 列位置 | 数据组织 | 对应表 | 字段说明 |
|--------|----------|--------|----------|
| C | 红球1号线系数 | t6 | 主球=1从球=1-16线系数=C列 |
| F | 红球2号线系数 | t6 | 主球=2从球=1-16线系数=F列 |
| I | 红球3号线系数 | t6 | 主球=3从球=1-16线系数=I列 |
| ... | 依此类推 | t6 | 每三列为一组最多33组 |
**说明**T6工作表每三列为一组数据每组有16行数据红球号码1-33主球蓝球号码1-16从球行号线系数在C、F、I、L...列。
**T7工作表红球组红球面系数数据**的数据结构如下:
| 列位置 | 数据组织 | 对应表 | 字段说明 |
|--------|----------|--------|----------|
| C | 红球1号面系数 | t7 | 主球=1从球=2-33面系数=C列 |
| F | 红球2号面系数 | t7 | 主球=2从球=1,3-33面系数=F列 |
| I | 红球3号面系数 | t7 | 主球=3从球=1-2,4-33面系数=I列 |
| ... | 依此类推 | t7 | 每三列为一组最多33组 |
**说明**T7工作表每三列为一组数据每组有33行数据红球号码1-33主球和从球面系数在C、F、I、L...列。**特殊处理**:排除自己和自己组合的情况。
**Excel数据结构**
- **第1行**标题行1号组、面系数、2号组、面系数...
- **第2行**从球1号的数据对应所有主球的面系数
- **第3行**从球2号的数据对应所有主球的面系数
- **...**
- **第34行**从球33号的数据对应所有主球的面系数
**处理逻辑**
- **1号组主球1**
- 球号列B列面系数列C列
- 读取所有行从B列获取从球号从C列获取面系数
- 排除对角线主球1=从球1的情况
- **2号组主球2**
- 球号列E列面系数列F列
- 读取所有行从E列获取从球号从F列获取面系数
- 排除对角线主球2=从球2的情况
- **依此类推...**
**关键改进**
- **动态读取球号**从Excel的球号列B、E、H...)读取实际球号,不依赖行号
- **完整数据覆盖**读取到Excel的最后一行确保包含33号球数据
- **不排除对角线**:读取到什么数据就插入什么数据,包括主球=从球的情况
- **最终生成**33×33=1089条记录
### 具体列映射
#### 红球表映射T1 Sheet
**history_all 表 (列A-G)**
- A列球号 (ballNumber)
- B列出现频次 (frequencyCount)
- C列出现频率% (frequencyPercentage)
- D列平均隐现期次 (averageInterval)
- E列最长隐现期次 (maxHiddenInterval)
- F列最多连出期次 (maxConsecutiveCount)
- G列点系数 (pointCoefficient)
**history_100 表 (列H-M球号使用A列)**
- A列球号 (ballNumber)
- H列出现频次 (frequencyCount)
- J列平均隐现期 (averageInterval)
- K列当前隐现期 (nowInterval)
- L列最多连出期次 (maxConsecutiveCount)
- M列点系数 (pointCoefficient)
**history_top 表 (列N-P)**
- N列排行 (no)
- O列球号 (ballNumber)
- P列点系数 (pointCoefficient)
**history_top_100 表 (列Q-S)**
- Q列排行 (no)
- R列球号 (ballNumber)
- S列点系数 (pointCoefficient)
#### 蓝球表映射T2 Sheet
**blue_history_all 表 (列A-G)**
- A列球号 (ballNumber)
- B列出现频次 (frequencyCount)
- C列出现频率% (frequencyPercentage)
- D列平均隐现期次 (averageInterval)
- E列最长隐现期次 (maxHiddenInterval)
- F列最多连出期次 (maxConsecutiveCount)
- G列点系数 (pointCoefficient)
**blue_history_100 表 (列H-M球号使用A列)**
- A列球号 (ballNumber)
- H列出现频次 (frequencyCount)
- J列平均隐现期 (averageInterval)
- K列当前隐现期 (nowInterval)
- L列最多连出期次 (maxConsecutiveCount)
- M列点系数 (pointCoefficient)
**blue_history_top 表 (列N-P)**
- N列排行 (no)
- O列球号 (ballNumber)
- P列点系数 (pointCoefficient)
**blue_history_top_100 表 (列Q-S)**
- Q列排行 (no)
- R列球号 (ballNumber)
- S列点系数 (pointCoefficient)
#### T3表映射T3 Sheet
**t3 表(红球线系数数据)**
- 数据组织每三列为一组每组33行数据
- 红球号码范围1-33主球和从球都是
- 线系数位置C、F、I、L...列
**数据映射**
- C列红球1号线系数主球=1从球=1-33线系数=C列
- F列红球2号线系数主球=2从球=1-33线系数=F列
- I列红球3号线系数主球=3从球=1-33线系数=I列
- 依此类推...
**字段映射**
- masterBallNumber主红球号码1-33
- slaveBallNumber从红球号码固定1-33对应行号
- lineCoefficient线系数每组第三列保留两位小数
#### T4表映射T4 Sheet
**t4 表(蓝球组红球的线系数)**
- 数据组织每三列为一组每组33行数据
- 蓝球号码范围1-16主球
- 红球号码范围1-33从球对应行号
- 线系数位置C、F、I、L...列
**数据映射**
- C列蓝球1号线系数主球=1从球=1-33线系数=C列
- F列蓝球2号线系数主球=2从球=1-33线系数=F列
- I列蓝球3号线系数主球=3从球=1-33线系数=I列
- 依此类推...
**字段映射**
- masterBallNumber蓝球号码1-16
- slaveBallNumber红球号码固定1-33对应行号
- lineCoefficient线系数每组第三列保留两位小数
#### T5表映射T5 Sheet
**t5 表(蓝球组蓝球的线系数)**
- 数据组织每三列为一组每组16行数据
- 蓝球号码范围1-16主球和从球都是
- 线系数位置C、F、I、L...列
**数据映射**
- C列蓝球1号线系数主球=1从球=1-16线系数=C列
- F列蓝球2号线系数主球=2从球=1-16线系数=F列
- I列蓝球3号线系数主球=3从球=1-16线系数=I列
- 依此类推...
**字段映射**
- masterBallNumber主蓝球号码1-16
- slaveBallNumber从蓝球号码固定1-16对应行号
- lineCoefficient线系数每组第三列保留两位小数
#### T6表映射T6 Sheet
**t6 表(红球组蓝球的线系数)**
- 数据组织每三列为一组每组16行数据
- 红球号码范围1-33主球
- 蓝球号码范围1-16从球
- 线系数位置C、F、I、L...列
**数据映射**
- C列红球1号线系数主球=1从球=1-16线系数=C列
- F列红球2号线系数主球=2从球=1-16线系数=F列
- I列红球3号线系数主球=3从球=1-16线系数=I列
- 依此类推...
**字段映射**
- masterBallNumber主红球号码1-33
- slaveBallNumber从蓝球号码固定1-16对应行号
- lineCoefficient线系数每组第三列保留两位小数
#### T7表映射T7 Sheet
**t7 表(红球组红球的面系数)**
- 数据组织每三列为一组每组33行数据
- 红球号码范围1-33主球和从球都是
- 面系数位置C、F、I、L...列
- 特殊处理:读取到什么数据就插入什么数据,包括对角线
**数据映射**
- C列红球1号面系数主球=1从球=1-33面系数=C列
- F列红球2号面系数主球=2从球=1-33面系数=F列
- I列红球3号面系数主球=3从球=1-33面系数=I列
- 依此类推...
**字段映射**
- masterBallNumber主红球号码1-33
- slaveBallNumber从红球号码1-33包括与主球相同的号码
- faceCoefficient面系数每组第三列保留两位小数
#### T8表映射T8 Sheet
**t8 表(红球组蓝球的面系数)**
- 数据组织每三列为一组每组16行数据
- 红球号码范围1-33主球
- 蓝球号码范围1-16从球
- 面系数位置C、F、I、L...列
**数据映射**
- C列红球1号面系数主球=1从球=1-16面系数=C列
- F列红球2号面系数主球=2从球=1-16面系数=F列
- I列红球3号面系数主球=3从球=1-16面系数=I列
- 依此类推...
**字段映射**
- masterBallNumber主红球号码1-33
- slaveBallNumber从蓝球号码固定1-16对应行号
- faceCoefficient面系数每组第三列保留两位小数
#### T10表映射T10 Sheet
**lottery_draws 表(彩票开奖信息)**
- 数据组织:标准表格结构,每行一条开奖记录
- 开奖期号Long类型主键
- 开奖日期Date类型支持多种格式
- 红球1-6Integer类型
- 蓝球Integer类型
**数据映射**
- A列开奖期号drawId
- B列开奖日期drawDate
- C列红球1redBall1
- D列红球2redBall2
- E列红球3redBall3
- F列红球4redBall4
- G列红球5redBall5
- H列红球6redBall6
- I列蓝球blueBall
**字段映射**
- drawId开奖期号Long类型主键
- drawDate开奖日期Date类型支持yyyy-MM-dd、yyyy/MM/dd等格式
- redBall1-redBall6红球1-6Integer类型
- blueBall蓝球Integer类型
**数据特性**
- 所有字段均为必填项
- 开奖期号为主键,不能重复
- 日期格式自动识别和转换
- 数据完整性验证
#### T11表映射T11 Sheet
**t11 表(蓝球组红球的面系数)**
- 数据组织每三列为一组每组33行数据
- 蓝球号码范围1-16主球
- 红球号码范围1-33从球
- 面系数位置C、F、I、L...列
**数据映射**
- C列蓝球1号面系数主球=1从球=1-33面系数=C列
- F列蓝球2号面系数主球=2从球=1-33面系数=F列
- I列蓝球3号面系数主球=3从球=1-33面系数=I列
- 依此类推...
**字段映射**
- masterBallNumber主蓝球号码1-16
- slaveBallNumber从红球号码固定1-33对应行号
- faceCoefficient面系数每组第三列保留两位小数
## 使用方法
### 1. API接口方式
#### 1.1 文件上传导入
```http
POST /api/excel/upload
Content-Type: multipart/form-data
参数:
- file: Excel文件 (.xlsx格式)
```
#### 1.2 文件路径导入
```http
POST /api/excel/import-by-path
Content-Type: application/x-www-form-urlencoded
参数:
- filePath: Excel文件的完整路径 (例如: D:/data/kaifa1.xlsx)
```
#### 1.3 获取导入说明
```http
GET /api/excel/import-info
```
### 2. 程序调用方式
```java
@Autowired
private ExcelImportService excelImportService;
// 方式1通过文件路径导入
String result = excelImportService.importExcelFileByPath("D:/data/kaifa1.xlsx");
// 方式2通过MultipartFile导入
String result = excelImportService.importExcelFile(multipartFile);
```
### 3. 测试方式
运行测试类:
```java
// 运行 ExcelImportTest 类中的测试方法
@Test
public void testImportExcelByPath() {
// 修改文件路径为实际路径
String filePath = "D:/code/xy-ai-cpzs/kaifa1.xlsx";
String result = excelImportService.importExcelFileByPath(filePath);
System.out.println("导入结果:" + result);
}
```
## 注意事项
1. **数据清空**:每次导入前会清空现有数据,请谨慎操作
2. **数据验证**:系统会验证数据的完整性,球号为空的记录会被跳过
3. **错误处理**:导入过程中如有错误会回滚操作并返回错误信息
4. **日志记录**:导入过程会记录详细日志,便于问题排查
5. **文件大小**建议文件大小不超过10MB
6. **并发限制**:避免同时进行多个导入操作
## 常见问题
### Q1: 提示"未找到T1/T2/T3/T4/T5/T6/T7/T8/T10/T11工作表"
**A**: 请检查Excel文件是否包含名为"T1"(红球数据)、"T2"(蓝球数据)、"T3"(红球线系数)、"T4"(蓝球组红球线系数)、"T5"(蓝球组蓝球线系数)、"T6"(红球组蓝球线系数)、"T7"(红球组红球面系数)、"T8"(红球组蓝球面系数)、"T10"(彩票开奖信息)和"T11"(蓝球组红球面系数)的工作表,注意区分大小写。如果缺少某个工作表,系统会跳过该部分数据并显示警告。
### Q2: 导入后数据不完整
**A**: 请检查Excel数据格式是否正确确保数值类型的列包含有效数字。
### Q3: 导入失败提示文件格式错误
**A**: 请确保文件是.xlsx格式不支持.xls格式。
### Q4: 如何查看导入日志
**A**: 导入过程中的日志会输出到控制台,可以通过查看应用日志了解详细信息。
### Q5: 报错"Cannot get a NUMERIC value from a STRING cell"
**A**: 这个错误已经修复。系统现在能够自动处理字符串类型的数值单元格,会尝试将字符串转换为数值。
### Q6: Excel中有公式单元格怎么办
**A**: 系统支持公式单元格,会自动读取公式计算后的结果值。
### Q7: 单元格为空怎么处理
**A**: 空白单元格会被自动跳过对应的字段值会设为null。
## 数据类型支持
系统支持以下类型的Excel单元格
- **数值类型** - 直接读取数值
- **字符串类型** - 尝试转换为数值(如果包含数字)
- **公式类型** - 读取公式计算结果
- **空白类型** - 设为null
- **其他类型** - 会记录警告日志并设为null
## 数据精度处理
- **浮点数字段**:自动保留两位小数(使用四舍五入)
- **整数字段**:直接转换为整数(去除小数部分)
- **特殊值处理**NaN和无穷大值会被设为null
示例:
- `123.456789``123.46`
- `12.1``12.10`
- `5``5.00`
## Swagger文档
启动应用后可以通过以下地址访问API文档
- Swagger UI: http://localhost:8123/api/swagger-ui.html
- Knife4j UI: http://localhost:8123/api/doc.html
在文档中可以直接测试Excel导入接口。

View File

@@ -0,0 +1,182 @@
# VIP兑换记录查询API使用说明
## 接口概述
本文档描述了VIP兑换记录查询相关的API接口主要用于查询用户的VIP兑换记录信息。
## 接口列表
### 1. 获取用户所有兑换记录
**接口地址:** `GET /vip-exchange-record/user/{userId}`
**接口描述:** 根据用户ID获取该用户的所有VIP兑换记录
**请求参数:**
- `userId` (必填): 用户ID路径参数必须大于0
**请求示例:**
```http
GET /vip-exchange-record/user/123
```
**响应示例:**
```json
{
"code": 0,
"message": "success",
"data": [
{
"id": 1,
"userId": 123,
"type": "月度会员",
"exchangeMode": 1,
"orderNo": 1234567890123456,
"orderAmount": 0,
"isUse": 1,
"exchangeTime": "2024-01-15T10:30:00",
"updateTime": "2024-01-15T10:30:00"
},
{
"id": 2,
"userId": 123,
"type": "年度会员",
"exchangeMode": 1,
"orderNo": 1234567890123457,
"orderAmount": 0,
"isUse": 1,
"exchangeTime": "2024-02-15T14:20:00",
"updateTime": "2024-02-15T14:20:00"
}
]
}
```
### 2. 分页获取用户兑换记录
**接口地址:** `GET /vip-exchange-record/user/{userId}/page`
**接口描述:** 根据用户ID分页获取该用户的VIP兑换记录
**请求参数:**
- `userId` (必填): 用户ID路径参数必须大于0
- `page` (可选): 页码从1开始默认为1
- `size` (可选): 每页大小默认为10最大100
**请求示例:**
```http
GET /vip-exchange-record/user/123/page?page=1&size=5
```
**响应示例:**
```json
{
"code": 0,
"message": "success",
"data": [
{
"id": 1,
"userId": 123,
"type": "月度会员",
"exchangeMode": 1,
"orderNo": 1234567890123456,
"orderAmount": 0,
"isUse": 1,
"exchangeTime": "2024-01-15T10:30:00",
"updateTime": "2024-01-15T10:30:00"
}
]
}
```
### 3. 获取兑换记录详情
**接口地址:** `GET /vip-exchange-record/{recordId}`
**接口描述:** 根据兑换记录ID获取详细信息
**请求参数:**
- `recordId` (必填): 兑换记录ID路径参数必须大于0
**请求示例:**
```http
GET /vip-exchange-record/1
```
**响应示例:**
```json
{
"code": 0,
"message": "success",
"data": {
"id": 1,
"userId": 123,
"type": "月度会员",
"exchangeMode": 1,
"orderNo": 1234567890123456,
"orderAmount": 0,
"isUse": 1,
"exchangeTime": "2024-01-15T10:30:00",
"updateTime": "2024-01-15T10:30:00"
}
}
```
## 数据字段说明
### VipExchangeRecord 字段说明
| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | Long | 兑换记录唯一标识符 |
| userId | Long | 用户ID |
| type | String | 会员类型(月度会员/年度会员) |
| exchangeMode | Integer | 兑换方式1-VIP码兑换 |
| orderNo | Long | 订单编号16位随机数字 |
| orderAmount | Integer | 订单金额(单位:分) |
| isUse | Integer | 是否已兑换0-未兑换1-已兑换) |
| exchangeTime | Date | 兑换时间 |
| updateTime | Date | 更新时间 |
## 响应状态码
| 状态码 | 说明 |
|--------|------|
| 0 | 成功 |
| 40000 | 请求参数错误 |
| 40400 | 请求数据不存在 |
| 50000 | 系统内部异常 |
## 错误响应示例
```json
{
"code": 40000,
"message": "用户ID不能为空且必须大于0",
"data": null
}
```
## 使用说明
1. **数据排序**: 所有查询结果都按照兑换时间倒序排列(最新的记录在前)
2. **分页查询**:
- 页码从1开始
- 每页大小限制在1-100之间
- 超出范围会使用默认值
3. **参数校验**:
- 用户ID和记录ID必须为正整数
- 参数错误会返回40000状态码
4. **异常处理**:
- 系统异常会返回50000状态码
- 详细错误信息会在message字段中说明
## 注意事项
1. 接口支持Swagger文档可通过 `/swagger-ui/index.html` 查看详细文档
2. 所有接口都有完整的日志记录,便于问题排查
3. 建议在生产环境中添加认证和权限控制
4. 分页查询采用内存分页,大数据量时建议使用数据库分页优化

BIN
kaifa1.xlsx Normal file

Binary file not shown.

259
mvnw vendored Normal file
View File

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

149
mvnw.cmd vendored Normal file
View File

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

BIN
nls-sample-16k.wav Normal file

Binary file not shown.

198
pom.xml Normal file
View File

@@ -0,0 +1,198 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xy</groupId>
<artifactId>xy-ai-cpzs</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>xy-ai-cpzs</name>
<description>xy-ai-cpzs</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java-sms</artifactId>
<version>3.1.1281</version>
</dependency>
<dependency>
<groupId>com.alibaba.nls</groupId>
<artifactId>nls-sdk-recognizer</artifactId>
<version>2.2.1</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.aliyun</groupId>-->
<!-- <artifactId>aliyun-java-sdk-core</artifactId>-->
<!-- <version>3.7.1</version>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; http://mvnrepository.com/artifact/com.alibaba/fastjson &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba</groupId>-->
<!-- <artifactId>fastjson</artifactId>-->
<!-- <version>1.2.83</version>-->
<!-- </dependency>-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<!-- 请将 'the-latest-version' 替换为最新版本号https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java -->
<version>2.20.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.37</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.12</version>
</dependency>
<!-- 分页插件 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>3.5.12</version> <!-- 确保版本和 MyBatis Plus 主包一致 -->
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- Excel读取依赖 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.4</version>
</dependency>
<!-- JWT依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- Redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 阿里云短信依赖 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>2.0.24</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea-openapi</artifactId>
<version>0.2.8</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea-util</artifactId>
<version>0.2.21</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>credentials-java</artifactId>
<version>0.2.4</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea</artifactId>
<version>1.2.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

272
sql/ddl.sql Normal file
View File

@@ -0,0 +1,272 @@
-- 聊天消息表
create database if not exists cpzs;
-- 切换库
use cpzs;
-- 创建历史数据表
CREATE TABLE IF NOT EXISTS `history_all` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
ballNumber INT NULL COMMENT '球号',
frequencyCount INT NULL COMMENT '出现频次',
frequencyPercentage FLOAT NULL COMMENT '出现频率百分比',
averageInterval FLOAT NULL COMMENT '平均隐现期(次)',
maxHiddenInterval INT NULL COMMENT '最长隐现期(次)',
maxConsecutiveCount INT NULL COMMENT '最多连出期(次)',
pointCoefficient FLOAT NULL COMMENT '点系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '全部历史数据表';
-- 创建最近100期数据表
CREATE TABLE IF NOT EXISTS `history_100` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
ballNumber INT NULL COMMENT '球号',
frequencyCount INT NULL COMMENT '出现频次',
averageInterval FLOAT NULL COMMENT '平均隐现期(次)',
nowInterval INT NULL COMMENT '当前隐现期(次)',
maxConsecutiveCount INT NULL COMMENT '最多连出期(次)',
pointCoefficient FLOAT NULL COMMENT '点系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '最近100期数据表';
-- 创建历史数据排行表
CREATE TABLE IF NOT EXISTS `history_top` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
no INT NULL COMMENT '排行',
ballNumber INT NULL COMMENT '球号',
pointCoefficient FLOAT NULL COMMENT '点系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '历史数据排行表';
-- 创建100期数据排行表
CREATE TABLE IF NOT EXISTS `history_top_100` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
no INT NULL COMMENT '排行',
ballNumber INT NULL COMMENT '球号',
pointCoefficient FLOAT NULL COMMENT '点系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '100期数据排行表';
-- 创建蓝球历史数据表
CREATE TABLE IF NOT EXISTS `blue_history_all` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
ballNumber INT NULL COMMENT '球号',
frequencyCount INT NULL COMMENT '出现频次',
frequencyPercentage FLOAT NULL COMMENT '出现频率百分比',
averageInterval FLOAT NULL COMMENT '平均隐现期(次)',
maxHiddenInterval INT NULL COMMENT '最长隐现期(次)',
maxConsecutiveCount INT NULL COMMENT '最多连出期(次)',
pointCoefficient FLOAT NULL COMMENT '点系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '蓝球全部历史数据表';
-- 创建蓝球最近100期数据表
CREATE TABLE IF NOT EXISTS `blue_history_100` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
ballNumber INT NULL COMMENT '球号',
frequencyCount INT NULL COMMENT '出现频次',
averageInterval FLOAT NULL COMMENT '平均隐现期(次)',
nowInterval INT NULL COMMENT '当前隐现期(次)',
maxConsecutiveCount INT NULL COMMENT '最多连出期(次)',
pointCoefficient FLOAT NULL COMMENT '点系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '蓝球最近100期数据表';
-- 创建蓝球历史数据排行表
CREATE TABLE IF NOT EXISTS `blue_history_top` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
no INT NULL COMMENT '排行',
ballNumber INT NULL COMMENT '球号',
pointCoefficient FLOAT NULL COMMENT '点系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '蓝球历史数据排行表';
-- 创建蓝球100期数据排行表
CREATE TABLE IF NOT EXISTS `blue_history_top_100` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
no INT NULL COMMENT '排行',
ballNumber INT NULL COMMENT '球号',
pointCoefficient FLOAT NULL COMMENT '点系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '蓝球100期数据排行表';
-- 创建t3表红球组红球的线系数
CREATE TABLE IF NOT EXISTS `t3` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
masterBallNumber INT NULL COMMENT '主球',
slaveBallNumber INT NULL COMMENT '从球',
lineCoefficient FLOAT NULL COMMENT '线系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 't3表红球组红球的线系数';
-- 创建t4表蓝球组红球的线系数
CREATE TABLE IF NOT EXISTS `t4` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
masterBallNumber INT NULL COMMENT '主球',
slaveBallNumber INT NULL COMMENT '从球',
lineCoefficient FLOAT NULL COMMENT '线系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 't4表蓝球组红球的线系数';
-- 创建t5表蓝球组蓝球的线系数
CREATE TABLE IF NOT EXISTS `t5` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
masterBallNumber INT NULL COMMENT '主球',
slaveBallNumber INT NULL COMMENT '从球',
lineCoefficient FLOAT NULL COMMENT '线系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 't5表蓝球组蓝球的线系数';
-- 创建t6表红球组蓝球的线系数
CREATE TABLE IF NOT EXISTS `t6` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
masterBallNumber INT NULL COMMENT '主球',
slaveBallNumber INT NULL COMMENT '从球',
lineCoefficient FLOAT NULL COMMENT '线系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 't6表红球组蓝球的线系数';
-- 创建t7表红球组红球的面系数
CREATE TABLE IF NOT EXISTS `t7` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
masterBallNumber INT NULL COMMENT '主球',
slaveBallNumber INT NULL COMMENT '从球',
faceCoefficient FLOAT NULL COMMENT '面系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 't7表红球组红球的面系数';
-- 创建t8表红球组蓝球的面系数
CREATE TABLE IF NOT EXISTS `t8` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
masterBallNumber INT NULL COMMENT '主球',
slaveBallNumber INT NULL COMMENT '从球',
faceCoefficient FLOAT NULL COMMENT '面系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 't8表红球组蓝球的面系数';
-- 创建t11表蓝球组红球的面系数
CREATE TABLE IF NOT EXISTS `t11` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
masterBallNumber INT NULL COMMENT '主球',
slaveBallNumber INT NULL COMMENT '从球',
faceCoefficient FLOAT NULL COMMENT '面系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 't11表蓝球组红球的面系数';
CREATE TABLE IF NOT EXISTS `lottery_draws` (
`drawId` BIGINT NOT NULL COMMENT '开奖期号' PRIMARY KEY,
`drawDate` DATE NOT NULL COMMENT '开奖日期',
`redBall1` INT NOT NULL COMMENT '红1',
`redBall2` INT NOT NULL COMMENT '红2',
`redBall3` INT NOT NULL COMMENT '红3',
`redBall4` INT NOT NULL COMMENT '红4',
`redBall5` INT NOT NULL COMMENT '红5',
`redBall6` INT NOT NULL COMMENT '红6',
`blueBall` INT NOT NULL COMMENT '蓝球'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '彩票开奖信息表';
-- 用户表
create table if not exists user
(
id bigint auto_increment comment 'id' primary key,
userName varchar(256) null comment '用户昵称',
userAccount varchar(256) not null comment '账号',
phone varchar(11) null comment '手机号',
userAvatar varchar(1024) null comment '用户头像',
gender tinyint null comment '性别',
userRole varchar(256) default 'user' not null comment '用户角色user / admin',
userPassword varchar(512) not null comment '密码',
isVip int default 0 not null comment '是否会员0-非会员1-会员',
vipExpire datetime null comment '会员到期时间',
`status` tinyint DEFAULT '0' COMMENT '状态0正常1不正常',
createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
isDelete tinyint default 0 not null comment '是否删除',
constraint uni_userAccount
unique (userAccount)
) comment '用户';
CREATE TABLE IF NOT EXISTS `predict_record` (
`id` BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
`userId` BIGINT NOT NULL COMMENT '用户ID',
`drawId` BIGINT NOT NULL COMMENT '开奖期号' ,
`drawDate` DATE NOT NULL COMMENT '开奖日期',
`redBall1` INT NOT NULL COMMENT '红1',
`redBall2` INT NOT NULL COMMENT '红2',
`redBall3` INT NOT NULL COMMENT '红3',
`redBall4` INT NOT NULL COMMENT '红4',
`redBall5` INT NOT NULL COMMENT '红5',
`redBall6` INT NOT NULL COMMENT '红6',
`blueBall` INT NOT NULL COMMENT '蓝球',
`predictStatus` VARCHAR(100) default '待开奖' NOT NULL COMMENT '预测状态(待开奖/已开奖)',
`predictResult` VARCHAR(100) default '待开奖' NOT NULL COMMENT '预测结果(未中奖/三等奖/二等奖/一等奖)',
`predictTime` datetime default CURRENT_TIMESTAMP not null comment '预测时间',
`bonus` BIGINT default 0 NOT NULL COMMENT '奖金'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '彩票开奖信息表';
CREATE TABLE IF NOT EXISTS `vip_code` (
`id` BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
`code` varchar(36) NOT NULL COMMENT '会员码',
`vipExpireTime` int not null COMMENT '会员有效月数(1/12)',
`vipNumber` int not NULL COMMENT '会员编号',
`isUse` int NOT NULL COMMENT '是否使用',
createdUserId bigint null comment '创建人',
createdUserName varchar(36) null comment '创建人名称',
usedUserId bigint null comment '使用人',
usedUserName varchar(36) null comment '使用人名称',
`createTime` datetime DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '创建时间',
`updateTime` datetime NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `Order_OrderNo_uindex` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会员码表';
INSERT INTO `vip_code` (`id`, `code`, `vipExpireTime`, `vipNumber`, `isUse`, `createTime`, `updateTime`) VALUES
(1, 'A3B4C5D6E7F8G9H1', 1, '000001', 0, NOW(), NOW()),
(2, 'I2J3K4L5M6N7O8P9', 12, '000002', 0, NOW(), NOW());
CREATE TABLE IF NOT EXISTS `vip_exchange_record` (
`id` BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
`userId` bigint NOT NULL COMMENT '用户ID',
`type` varchar(36) NOT NULL COMMENT '月度会员/年度会员',
`exchangeMode` int not null COMMENT '兑换方式',
`orderNo` bigint not NULL COMMENT '订单编号',
`orderAmount` int not NULL COMMENT '订单金额',
`isUse` int NOT NULL COMMENT '是否兑换(未兑换/已兑换)',
`exchangeTime` datetime DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '兑换时间',
`updateTime` datetime DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会员兑换表';
CREATE TABLE IF NOT EXISTS `operation_history` (
`id` BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
`userId` BIGINT NOT NULL COMMENT '操作用户ID',
`operationType` VARCHAR(50) NOT NULL COMMENT '操作类型(批量生成会员码/获取可用会员码/Excel导入等',
`operationModule` INTEGER NOT NULL COMMENT '操作模块0-会员码管理/1-Excel导入管理等',
`operationResult` VARCHAR(20) NOT NULL COMMENT '操作结果(成功/失败)',
`resultMessage` TEXT COMMENT '结果消息',
`operationTime` DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '操作时间',
`updateTime` DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='操作历史记录表';
CREATE TABLE IF NOT EXISTS `chat_message`
(
id BIGINT AUTO_INCREMENT COMMENT 'id' PRIMARY KEY,
`conversationId` varchar(64) NULL COMMENT '会话ID',
`studentId` varchar(64) NULL COMMENT '用户ID关联用户表',
`messageType` varchar(64) NULL COMMENT '消息类型(如: 用户提问、AI回答',
`content` varchar(1024) NULL COMMENT '消息内容',
`createTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`isDelete` tinyint NOT NULL DEFAULT 0 COMMENT '是否删除 0-未删除 1-已删除'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='聊天消息表';

View File

@@ -0,0 +1,42 @@
package com.xy.xyaicpzs;
import com.alibaba.dashscope.app.*;
import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import io.reactivex.Flowable;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void streamCall() throws NoApiKeyException, InputRequiredException {
ApplicationParam param = ApplicationParam.builder()
// 若没有配置环境变量可用百炼API Key将下行替换为.apiKey("sk-xxx")。但不建议在生产环境中直接将API Key硬编码到代码中以减少API Key泄露风险。
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
// 替换为实际的应用 ID
.appId("ec08d5b81ca248e8834228c1133e2c78")
.prompt("你是谁")
// 增量输出
.incrementalOutput(true)
.build();
Application application = new Application();
// .streamCall流式输出内容
Flowable<ApplicationResult> result = application.streamCall(param);
result.blockingForEach(data -> {
System.out.printf("%s\n",
data.getOutput().getText());
// System.out.println("session_id: " + data.getOutput().getSessionId());
});
}
public static void main(String[] args) {
try {
streamCall();
} catch (ApiException | NoApiKeyException | InputRequiredException e) {
System.out.printf("Exception: %s", e.getMessage());
System.out.println("请参考文档https://help.aliyun.com/zh/model-studio/developer-reference/error-code");
}
System.exit(0);
}
}

View File

@@ -0,0 +1,54 @@
// This file is auto-generated, don't edit it. Thanks.
package com.xy.xyaicpzs;
import com.aliyun.tea.*;
public class Sample {
/**
* <b>description</b> :
* <p>使用凭据初始化账号Client</p>
* @return Client
*
* @throws Exception
*/
public static com.aliyun.dysmsapi20170525.Client createClient() throws Exception {
// 工程代码建议使用更安全的无AK方式凭据配置方式请参见https://help.aliyun.com/document_detail/378657.html。
com.aliyun.credentials.Client credential = new com.aliyun.credentials.Client();
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
.setCredential(credential);
// Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
config.endpoint = "dysmsapi.aliyuncs.com";
return new com.aliyun.dysmsapi20170525.Client(config);
}
public static void main(String[] args_) throws Exception {
com.aliyun.dysmsapi20170525.Client client = Sample.createClient();
com.aliyun.dysmsapi20170525.models.SendSmsRequest sendSmsRequest = new com.aliyun.dysmsapi20170525.models.SendSmsRequest()
.setSignName("西安精彩数据服务社")
.setTemplateCode("SMS_489840017")
.setPhoneNumbers("13868246742")
.setTemplateParam("{\"code\":\"1234\"}");
com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
try {
// 复制代码运行请自行打印 API 的返回值
client.sendSmsWithOptions(sendSmsRequest, runtime);
} catch (TeaException error) {
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
// 错误 message
System.out.println(error.getMessage());
// 诊断地址
System.out.println(error.getData().get("Recommend"));
com.aliyun.teautil.Common.assertAsString(error.message);
} catch (Exception _error) {
TeaException error = new TeaException(_error.getMessage(), _error);
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
// 错误 message
System.out.println(error.getMessage());
// 诊断地址
System.out.println(error.getData().get("Recommend"));
com.aliyun.teautil.Common.assertAsString(error.message);
}
}
}

View File

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

View File

@@ -0,0 +1,19 @@
package com.xy.xyaicpzs.common;
import lombok.Data;
import java.io.Serializable;
/**
* 删除请求
*/
@Data
public class DeleteRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
private Long id;
}

View File

@@ -0,0 +1,39 @@
package com.xy.xyaicpzs.common;
/**
* 自定义错误码
*/
public enum ErrorCode {
SUCCESS(0, "ok"),
PARAMS_ERROR(40000, "请求参数错误"),
NOT_LOGIN_ERROR(40100, "您还未登录,请登录后操作"),
NO_AUTH_ERROR(40101, "无权限"),
NOT_FOUND_ERROR(40400, "请求数据不存在"),
FORBIDDEN_ERROR(40300, "禁止访问"),
SYSTEM_ERROR(50000, "系统内部异常"),
OPERATION_ERROR(50001, "操作失败");
/**
* 状态码
*/
private final int code;
/**
* 信息
*/
private final String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}

View File

@@ -0,0 +1,20 @@
package com.xy.xyaicpzs.common;
import lombok.Data;
/**
* 分页请求
*/
@Data
public class PageRequest {
/**
* 当前页号
*/
private long current = 1;
/**
* 页面大小
*/
private long pageSize = 10;
}

View File

@@ -0,0 +1,51 @@
package com.xy.xyaicpzs.common;
import com.xy.xyaicpzs.common.response.ApiResponse;
/**
* 返回工具类
*/
public class ResultUtils {
/**
* 成功
*
* @param data
* @param <T>
* @return
*/
public static <T> ApiResponse<T> success(T data) {
return ApiResponse.success(data);
}
/**
* 失败
*
* @param errorCode
* @return
*/
public static <T> ApiResponse<T> error(ErrorCode errorCode) {
return ApiResponse.error(errorCode.getMessage());
}
/**
* 失败
*
* @param code
* @param message
* @return
*/
public static <T> ApiResponse<T> error(int code, String message) {
return ApiResponse.error(message);
}
/**
* 失败
*
* @param errorCode
* @return
*/
public static <T> ApiResponse<T> error(ErrorCode errorCode, String message) {
return ApiResponse.error(message);
}
}

View File

@@ -0,0 +1,24 @@
package com.xy.xyaicpzs.common.requset;
import lombok.Data;
import java.util.List;
/**
* 球号分析请求对象
*/
@Data
public class BallAnalysisRequest {
private String level;
private List<Integer> redBalls;
private Integer blueBall;
@Override
public String toString() {
return "BallAnalysisRequest{" +
"level='" + level + '\'' +
", redBalls=" + redBalls +
", blueBall=" + blueBall +
'}';
}
}

View File

@@ -0,0 +1,29 @@
package com.xy.xyaicpzs.common.requset;
import lombok.Data;
import java.util.List;
/**
* 蓝球分析请求对象
*/
@Data
public class BlueBallAnalysisRequest {
private String level;
private List<Integer> predictedRedBalls;
private List<Integer> predictedBlueBalls;
private List<Integer> lastRedBalls;
private Integer lastBlueBall;
@Override
public String toString() {
return "BlueBallAnalysisRequest{" +
"level='" + level + '\'' +
", predictedRedBalls=" + predictedRedBalls +
", predictedBlueBalls=" + predictedBlueBalls +
", lastRedBalls=" + lastRedBalls +
", lastBlueBall=" + lastBlueBall +
'}';
}
}

View File

@@ -0,0 +1,26 @@
package com.xy.xyaicpzs.common.requset;
import lombok.Data;
import java.util.List;
/**
* 跟随球号分析请求对象
*/
@Data
public class FallowBallAnalysisRequest {
private String level;
private List<Integer> firstThreeRedBalls;
private List<Integer> lastSixRedBalls;
private Integer blueBall;
@Override
public String toString() {
return "FallowBallAnalysisRequest{" +
"level='" + level + '\'' +
", firstThreeRedBalls=" + firstThreeRedBalls +
", lastSixRedBalls=" + lastSixRedBalls +
", blueBall=" + blueBall +
'}';
}
}

View File

@@ -0,0 +1,20 @@
package com.xy.xyaicpzs.common.requset;
import lombok.Data;
/**
* 生成会员码请求
*/
@Data
public class GenerateVipCodesRequest {
/**
* 生成数量
*/
private Integer numCodes;
/**
* 会员有效月数
*/
private Integer vipExpireTime;
}

View File

@@ -0,0 +1,23 @@
package com.xy.xyaicpzs.common.requset;
import com.xy.xyaicpzs.common.PageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 预测记录查询请求
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class PredictRecordQueryRequest extends PageRequest {
/**
* 用户ID
*/
private Long userId;
/**
* 预测状态(待开奖/未中奖/已中奖)
*/
private String predictStatus;
}

View File

@@ -0,0 +1,20 @@
package com.xy.xyaicpzs.common.requset;
import lombok.Data;
/**
* 会员码激活请求
*/
@Data
public class VipCodeActivateRequest {
/**
* 用户ID
*/
private Long userId;
/**
* 会员码
*/
private String code;
}

View File

@@ -0,0 +1,74 @@
package com.xy.xyaicpzs.common.requset;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
/**
* 会员码查询请求
*/
@Data
@Schema(description = "会员码查询请求")
public class VipCodeQueryRequest {
/**
* 当前页码
*/
@Schema(description = "当前页码")
private long current = 1;
/**
* 页面大小
*/
@Schema(description = "页面大小")
private long pageSize = 10;
/**
* 会员码
*/
@Schema(description = "会员码")
private String code;
/**
* 会员有效月数(1/12)
*/
@Schema(description = "会员有效月数")
private Integer vipExpireTime;
/**
* 是否使用0-未使用1-已使用
*/
@Schema(description = "是否使用0-未使用1-已使用")
private Integer isUse;
/**
* 创建人ID
*/
@Schema(description = "创建人ID")
private Long createdUserId;
/**
* 创建人名称
*/
@Schema(description = "创建人名称")
private String createdUserName;
/**
* 使用人ID
*/
@Schema(description = "使用人ID")
private Long usedUserId;
/**
* 开始时间
*/
@Schema(description = "开始时间")
private Date startTime;
/**
* 结束时间
*/
@Schema(description = "结束时间")
private Date endTime;
}

View File

@@ -0,0 +1,29 @@
package com.xy.xyaicpzs.common.response;
import lombok.Data;
/**
* API响应对象
*/
@Data
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.success = true;
response.message = "操作成功";
response.data = data;
return response;
}
public static <T> ApiResponse<T> error(String message) {
ApiResponse<T> response = new ApiResponse<>();
response.success = false;
response.message = message;
return response;
}
}

View File

@@ -0,0 +1,67 @@
package com.xy.xyaicpzs.common.response;
import lombok.Data;
import java.util.List;
/**
* 分页响应对象
* @param <T> 数据类型
*/
@Data
public class PageResponse<T> {
/**
* 数据列表
*/
private List<T> records;
/**
* 总记录数
*/
private Long total;
/**
* 当前页码
*/
private Integer page;
/**
* 每页大小
*/
private Integer size;
/**
* 总页数
*/
private Integer totalPages;
/**
* 是否有下一页
*/
private Boolean hasNext;
/**
* 是否有上一页
*/
private Boolean hasPrevious;
public PageResponse() {}
public PageResponse(List<T> records, Long total, Integer page, Integer size) {
this.records = records;
this.total = total;
this.page = page;
this.size = size;
this.totalPages = (int) Math.ceil((double) total / size);
this.hasNext = page < totalPages;
this.hasPrevious = page > 1;
}
/**
* 创建分页响应对象
*/
public static <T> PageResponse<T> of(List<T> records, Long total, Integer page, Integer size) {
return new PageResponse<>(records, total, page, size);
}
}

View File

@@ -0,0 +1,27 @@
package com.xy.xyaicpzs.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
// * 全局跨域配置
*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 覆盖所有请求
registry.addMapping("/**")
// 允许发送 Cookie
.allowCredentials(true)
// 放行哪些域名(必须用 patterns否则 * 会和 allowCredentials 冲突)
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
// 确保SSE相关头信息能被客户端访问
.exposedHeaders("*", HttpHeaders.CACHE_CONTROL, HttpHeaders.CONTENT_TYPE);
}
}

View File

@@ -0,0 +1,31 @@
package com.xy.xyaicpzs.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MyBatis Plus 配置
*
* @author lihanqi
*/
@Configuration
@MapperScan("com.xy.xyaicpzs.mapper")
public class MyBatisPlusConfig {
/**
* 拦截器配置
*
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

View File

@@ -0,0 +1,22 @@
package com.xy.xyaicpzs.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* ObjectMapper配置类
*/
@Configuration
public class ObjectMapperConfig {
/**
* 配置ObjectMapper Bean
*
* @return ObjectMapper实例
*/
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}

View File

@@ -0,0 +1,38 @@
package com.xy.xyaicpzs.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis配置类
*/
@Configuration
public class RedisConfig {
/**
* 自定义RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置连接工厂
redisTemplate.setConnectionFactory(connectionFactory);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// 使用GenericJackson2JsonRedisSerializer来序列化和反序列化redis的value值
GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
redisTemplate.setValueSerializer(jsonSerializer);
redisTemplate.setHashValueSerializer(jsonSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}

View File

@@ -0,0 +1,22 @@
package com.xy.xyaicpzs.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* RestTemplate配置类
*/
@Configuration
public class RestTemplateConfig {
/**
* 配置RestTemplate Bean
*
* @return RestTemplate实例
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

View File

@@ -0,0 +1,12 @@
package com.xy.xyaicpzs.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 定时任务配置类
*/
@Configuration
@EnableScheduling
public class ScheduleConfig {
}

View File

@@ -0,0 +1,36 @@
package com.xy.xyaicpzs.constant;
/**
* 用户常量
*/
public interface UserConstant {
/**
* 用户登录态键
*/
String USER_LOGIN_STATE = "userLoginState";
/**
* 系统用户 id虚拟用户
*/
long SYSTEM_USER_ID = 0;
// region 权限
/**
* 默认权限
*/
String DEFAULT_ROLE = "user";
/**
* 管理员权限
*/
String ADMIN_ROLE = "admin";
/**
* 超级管理员权限
*/
String SUPER_ADMIN_ROLE = "superAdmin";
// endregion
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,126 @@
package com.xy.xyaicpzs.controller;
import com.alibaba.dashscope.app.Application;
import com.alibaba.dashscope.app.ApplicationParam;
import com.alibaba.dashscope.app.ApplicationResult;
import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.xy.xyaicpzs.domain.entity.ChatMessage;
import com.xy.xyaicpzs.service.ChatMessageService;
import io.reactivex.Flowable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.atomic.AtomicReference;
@RestController
public class ChatController {
@Autowired
private ChatMessageService chatMessageService;
@Value("${dashscope.api-key}")
private String dashscopeApiKey;
/**
* SSE流式聊天接口
* @param message 用户消息
* @param conversationId 会话ID
* @param userId 用户ID
* @return SseEmitter
*/
@GetMapping("/chat/sse")
public SseEmitter chatSseEmitter(
@RequestParam String message,
@RequestParam(required = false) String conversationId,
@RequestParam(required = false) String userId) {
// 保存用户消息到数据库
saveMessage(conversationId, userId, "USER", message);
// 创建一个超时时间较长的 SseEmitter
SseEmitter emitter = new SseEmitter(180000L); // 3分钟超时
// 用于收集完整的AI回复
AtomicReference<StringBuilder> fullResponseRef = new AtomicReference<>(new StringBuilder());
try {
// 设置AI参数
ApplicationParam param = ApplicationParam.builder()
.apiKey(dashscopeApiKey) // 使用配置文件中的API密钥
.appId("ec08d5b81ca248e8834228c1133e2c78")
.prompt(message)
.incrementalOutput(true)
.build();
Application application = new Application();
// 流式调用
Flowable<ApplicationResult> result = application.streamCall(param);
result.subscribe(
// 处理每条消息
data -> {
try {
String text = data.getOutput().getText();
// 发送数据到客户端
emitter.send(text);
// 收集完整响应
fullResponseRef.get().append(text);
} catch (IOException e) {
emitter.completeWithError(e);
}
},
// 处理错误
error -> {
emitter.completeWithError(error);
System.out.println("错误: " + error.getMessage());
},
// 处理完成
() -> {
// 保存AI回复到数据库
String fullResponse = fullResponseRef.get().toString();
saveMessage(conversationId, userId, "AI", fullResponse);
emitter.complete();
}
);
} catch (ApiException | NoApiKeyException | InputRequiredException e) {
try {
emitter.send("错误: " + e.getMessage());
emitter.complete();
} catch (IOException ex) {
emitter.completeWithError(ex);
}
System.out.println("异常: " + e.getMessage());
}
return emitter;
}
/**
* 保存消息到数据库
* @param conversationId 会话ID
* @param userId 用户ID
* @param messageType 消息类型(USER/AI)
* @param content 消息内容
*/
private void saveMessage(String conversationId, String userId, String messageType, String content) {
ChatMessage chatMessage = new ChatMessage();
chatMessage.setConversationId(conversationId);
chatMessage.setStudentId(userId);
chatMessage.setMessageType(messageType);
chatMessage.setContent(content);
chatMessage.setCreateTime(new Date());
chatMessage.setUpdateTime(new Date());
chatMessage.setIsDelete(0);
chatMessageService.save(chatMessage);
}
}

View File

@@ -0,0 +1,138 @@
package com.xy.xyaicpzs.controller;
import com.xy.xyaicpzs.common.ErrorCode;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.common.requset.PredictRecordQueryRequest;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.common.response.PageResponse;
import com.xy.xyaicpzs.domain.entity.PredictRecord;
import com.xy.xyaicpzs.domain.vo.UserPredictStatVO;
import com.xy.xyaicpzs.service.DataAnalysisService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* 数据分析控制器
* 提供用户预测数据统计分析的API接口
*/
@Slf4j
@RestController
@RequestMapping("/data-analysis")
@Tag(name = "数据分析", description = "用户预测数据统计分析API")
public class DataAnalysisController {
@Autowired
private DataAnalysisService dataAnalysisService;
/**
* 获取用户预测统计数据
* @param userId 用户ID
* @return 用户预测统计数据
*/
@GetMapping("/user-predict-stat/{userId}")
@Operation(summary = "获取用户预测统计数据", description = "根据用户ID获取该用户的预测次数、待开奖次数、命中次数、命中率等统计数据")
public ApiResponse<UserPredictStatVO> getUserPredictStat(
@Parameter(description = "用户ID例如1001", required = true)
@PathVariable Long userId) {
try {
log.info("接收到获取用户预测统计数据请求用户ID={}", userId);
// 调用服务获取用户预测统计数据
UserPredictStatVO result = dataAnalysisService.getUserPredictStat(userId);
log.info("获取用户预测统计数据完成用户ID{},预测次数:{},待开奖次数:{},命中次数:{},命中率:{}",
userId, result.getPredictCount(), result.getPendingCount(),
result.getHitCount(), result.getHitRate());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取用户预测统计数据失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取用户预测统计数据失败:" + e.getMessage());
}
}
/**
* 手动触发处理待开奖记录
* @return 处理结果
*/
@PostMapping("/process-pending")
@Operation(summary = "手动处理待开奖记录", description = "手动触发处理待开奖的预测记录,匹配开奖结果并更新中奖状态")
public ApiResponse<String> processPendingPredictions() {
try {
log.info("接收到手动处理待开奖记录请求");
// 调用服务处理待开奖记录
int processedCount = dataAnalysisService.processPendingPredictions();
String message = String.format("处理完成,共处理%d条待开奖记录", processedCount);
log.info("手动处理待开奖记录完成:{}", message);
return ResultUtils.success(message);
} catch (Exception e) {
log.error("手动处理待开奖记录失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "处理待开奖记录失败:" + e.getMessage());
}
}
/**
* 按条件查询预测记录
* @param request 查询条件
* @return 分页预测记录
*/
@PostMapping("/query-predict-records")
@Operation(summary = "按条件查询预测记录", description = "根据用户ID和预测状态(待开奖/未中奖/已中奖)筛选预测记录,支持分页查询")
public ApiResponse<PageResponse<PredictRecord>> queryPredictRecords(@RequestBody PredictRecordQueryRequest request) {
try {
log.info("接收到按条件查询预测记录请求userId={}, predictStatus={}, current={}, pageSize={}",
request.getUserId(), request.getPredictStatus(), request.getCurrent(), request.getPageSize());
// 调用服务查询预测记录
PageResponse<PredictRecord> result = dataAnalysisService.queryPredictRecords(request);
log.info("按条件查询预测记录完成,总记录数:{}", result.getTotal());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("按条件查询预测记录失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "查询预测记录失败:" + e.getMessage());
}
}
/**
* 获取所有预测记录总数
* @return 预测记录总数
*/
@GetMapping("/total-predict-count")
@Operation(summary = "获取预测记录总数", description = "获取系统中所有用户的预测记录总数")
public ApiResponse<Map<String, Long>> getTotalPredictCount() {
try {
log.info("接收到获取预测记录总数请求");
// 调用服务获取预测记录总数
long totalCount = dataAnalysisService.getTotalPredictCount();
Map<String, Long> result = new HashMap<>();
result.put("totalCount", totalCount);
log.info("获取预测记录总数完成,总数:{}", totalCount);
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取预测记录总数失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取预测记录总数失败:" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,156 @@
package com.xy.xyaicpzs.controller;
import com.xy.xyaicpzs.common.ErrorCode;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.domain.entity.User;
import com.xy.xyaicpzs.service.ExcelImportService;
import com.xy.xyaicpzs.service.OperationHistoryService;
import com.xy.xyaicpzs.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
* Excel数据导入控制器
*/
@Slf4j
@RestController
@RequestMapping("/excel")
@Tag(name = "Excel数据导入", description = "Excel数据导入相关接口")
public class ExcelImportController {
@Autowired
private ExcelImportService excelImportService;
@Autowired
private UserService userService;
@Autowired
private OperationHistoryService operationHistoryService;
/**
* 上传Excel文件并导入数据
*/
@PostMapping("/upload")
@Operation(summary = "上传Excel文件导入数据", description = "上传包含T1、T2、T3、T4、T5、T6和T7 sheet的Excel文件将红球、蓝球、线系数和面系数数据分别导入到十二个数据库表中")
public ApiResponse<String> uploadExcelFile(
@Parameter(description = "Excel文件(.xlsx格式)", required = true)
@RequestParam("file") MultipartFile file, HttpServletRequest httpServletRequest) {
User loginUser = userService.getLoginUser(httpServletRequest);
if (!userService.isAdmin(loginUser)){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "无权限");
}
Long userId = loginUser.getId();
String userName = loginUser.getUserName();
String fileName = file.getOriginalFilename();
log.info("接收到Excel文件上传请求文件名{}", fileName);
try {
String message = excelImportService.importExcelFile(file);
// 记录操作历史 - 成功
String resultMessage = String.format("%s成功上传并导入Excel文件%s导入结果%s", userName, fileName, message);
operationHistoryService.recordOperation(userId, "Excel数据导入", 1, "成功", resultMessage);
return ResultUtils.success(message);
} catch (Exception e) {
log.error("Excel文件导入失败文件名{},错误:{}", fileName, e.getMessage(), e);
// 记录操作历史 - 失败
String resultMessage = String.format("%sExcel文件导入失败%s文件名%s错误原因%s", userName, fileName, fileName, e.getMessage());
operationHistoryService.recordOperation(userId, "Excel数据导入", 1, "失败", resultMessage);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "Excel文件导入失败" + e.getMessage());
}
}
/**
* 上传Excel文件并导入开奖数据
*/
@PostMapping("/upload-lottery-draws")
@Operation(summary = "上传Excel文件导入开奖数据", description = "上传包含T10工作表的Excel文件只导入开奖数据到lottery_draws表")
public ApiResponse<String> uploadLotteryDrawsFile(
@Parameter(description = "包含T10工作表的Excel文件(.xlsx格式)", required = true)
@RequestParam("file") MultipartFile file, HttpServletRequest httpServletRequest) {
User loginUser = userService.getLoginUser(httpServletRequest);
if (!userService.isAdmin(loginUser)){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "无权限");
}
Long userId = loginUser.getId();
String userName = loginUser.getUserName();
String fileName = file.getOriginalFilename();
log.info("接收到开奖数据上传请求,文件名:{}", fileName);
try {
String message = excelImportService.importLotteryDrawsFile(file);
// 记录操作历史 - 成功
String resultMessage = String.format("%s成功上传并导入开奖数据文件%s导入结果%s", userName, fileName, message);
operationHistoryService.recordOperation(userId, "开奖数据导入", 1, "成功", resultMessage);
return ResultUtils.success(message);
} catch (Exception e) {
log.error("开奖数据文件导入失败,文件名:{},错误:{}", fileName, e.getMessage(), e);
// 记录操作历史 - 失败
String resultMessage = String.format("%s开奖数据文件导入失败%s文件名%s错误原因%s", userName, fileName, fileName, e.getMessage());
operationHistoryService.recordOperation(userId, "开奖数据导入", 1, "失败", resultMessage);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "开奖数据文件导入失败:" + e.getMessage());
}
}
/**
* 上传Excel文件并追加导入开奖数据
*/
@PostMapping("/append-lottery-draws")
@Operation(summary = "上传Excel文件追加导入开奖数据", description = "上传包含T10工作表的Excel文件追加导入开奖数据不清空现有数据跳过重复期号")
public ApiResponse<String> appendLotteryDrawsFile(
@Parameter(description = "包含T10工作表的Excel文件(.xlsx格式)", required = true)
@RequestParam("file") MultipartFile file, HttpServletRequest httpServletRequest) {
User loginUser = userService.getLoginUser(httpServletRequest);
if (!userService.isAdmin(loginUser)){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "无权限");
}
Long userId = loginUser.getId();
String userName = loginUser.getUserName();
String fileName = file.getOriginalFilename();
log.info("接收到开奖数据追加上传请求,文件名:{}", fileName);
try {
String message = excelImportService.appendLotteryDrawsFile(file);
// 记录操作历史 - 成功
String resultMessage = String.format("%s成功追加导入开奖数据文件%s导入结果%s", userName, fileName, message);
operationHistoryService.recordOperation(userId, "开奖数据追加导入", 1, "成功", resultMessage);
return ResultUtils.success(message);
} catch (Exception e) {
log.error("开奖数据追加导入失败,文件名:{},错误:{}", fileName, e.getMessage(), e);
// 记录操作历史 - 失败
String resultMessage = String.format("%s开奖数据追加导入失败%s文件名%s错误原因%s", userName, fileName, fileName, e.getMessage());
operationHistoryService.recordOperation(userId, "开奖数据追加导入", 1, "失败", resultMessage);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "开奖数据追加导入失败:" + e.getMessage());
}
}
/**
* 获取导入说明
*/
@GetMapping("/import-info")
@Operation(summary = "获取导入说明", description = "获取Excel数据导入的详细说明")
public String getImportInfo() {
return excelImportService.getImportInfo();
}
}

View File

@@ -0,0 +1,15 @@
package com.xy.xyaicpzs.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/health")
public class HealthController {
@GetMapping
public String healthCheck() {
return "ok";
}
}

View File

@@ -0,0 +1,108 @@
package com.xy.xyaicpzs.controller;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.service.CozeAuthService;
import com.xy.xyaicpzs.util.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* JWT令牌控制器
*/
@RestController
@RequestMapping("/jwt")
@Tag(name = "JWT接口", description = "提供JWT令牌生成功能")
public class JwtController {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private CozeAuthService cozeAuthService;
/**
* 生成JWT令牌
*
* @param expireSeconds 过期时间(秒)
* @param sessionName 会话名称(可选)
* @param deviceId 设备ID可选
* @return JWT令牌
*/
@GetMapping("/token")
@Operation(summary = "生成JWT令牌", description = "生成Coze API访问所需的JWT令牌")
public ApiResponse<Map<String, String>> generateToken(
@Parameter(description = "过期时间(秒)") @RequestParam(defaultValue = "600") int expireSeconds,
@Parameter(description = "会话名称(可选)") @RequestParam(required = false) String sessionName,
@Parameter(description = "设备ID可选") @RequestParam(required = false) String deviceId) {
try {
String token = jwtUtil.generateToken(expireSeconds, sessionName, deviceId);
Map<String, String> result = new HashMap<>();
result.put("token", token);
return ResultUtils.success(result);
} catch (Exception e) {
return ResultUtils.error(50000, "JWT生成失败: " + e.getMessage());
}
}
/**
* 通过JWT获取访问令牌
*
* @param jwt JWT令牌
* @param durationSeconds 访问令牌有效期默认为86400秒1天
* @return 包含访问令牌和过期时间的信息
*/
@PostMapping("/access-token")
@Operation(summary = "获取访问令牌", description = "通过JWT获取Coze API的OAuth访问令牌")
public ApiResponse<Map<String, Object>> getAccessToken(
@Parameter(description = "JWT令牌") @RequestParam String jwt,
@Parameter(description = "令牌有效期(秒)") @RequestParam(defaultValue = "86400") Integer durationSeconds) {
try {
Map<String, Object> tokenInfo = cozeAuthService.getAccessToken(jwt, durationSeconds);
return ResultUtils.success(tokenInfo);
} catch (Exception e) {
return ResultUtils.error(50000, "获取访问令牌失败: " + e.getMessage());
}
}
/**
* 一站式获取访问令牌生成JWT并立即获取访问令牌
*
* @param jwtExpireSeconds JWT过期时间
* @param sessionName 会话名称(可选)
* @param deviceId 设备ID可选
* @param tokenDurationSeconds 访问令牌有效期(秒)
* @return 包含JWT、访问令牌和过期时间的信息
*/
@PostMapping("/one-step-token")
@Operation(summary = "一站式获取访问令牌", description = "生成JWT并立即获取Coze API的OAuth访问令牌")
public ApiResponse<Map<String, Object>> getOneStepToken(
@Parameter(description = "JWT过期时间") @RequestParam(defaultValue = "600") int jwtExpireSeconds,
@Parameter(description = "会话名称(可选)") @RequestParam(required = false) String sessionName,
@Parameter(description = "设备ID可选") @RequestParam(required = false) String deviceId,
@Parameter(description = "访问令牌有效期(秒)") @RequestParam(defaultValue = "86400") Integer tokenDurationSeconds) {
try {
// 生成JWT令牌
String jwt = jwtUtil.generateToken(jwtExpireSeconds, sessionName, deviceId);
// 获取访问令牌
Map<String, Object> tokenInfo = cozeAuthService.getAccessToken(jwt, tokenDurationSeconds);
// 合并结果
tokenInfo.put("jwt", jwt);
return ResultUtils.success(tokenInfo);
} catch (Exception e) {
return ResultUtils.error(50000, "获取访问令牌失败: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,96 @@
package com.xy.xyaicpzs.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xy.xyaicpzs.common.ErrorCode;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.domain.entity.OperationHistory;
import com.xy.xyaicpzs.domain.entity.User;
import com.xy.xyaicpzs.service.OperationHistoryService;
import com.xy.xyaicpzs.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 操作历史管理控制器
*/
@RestController
@RequestMapping("/operation-history")
@Slf4j
@Tag(name = "操作历史管理", description = "操作历史记录相关接口")
public class OperationHistoryController {
@Autowired
private OperationHistoryService operationHistoryService;
@Autowired
private UserService userService;
/**
* 获取操作历史记录
* 支持按操作模块和操作结果进行筛选
*/
@GetMapping("/list")
@Operation(summary = "获取操作历史记录", description = "获取操作历史记录,支持按操作模块、操作结果筛选和结果信息模糊搜索")
public ApiResponse<List<OperationHistory>> getOperationHistory(
@Parameter(description = "操作模块0-会员码管理/1-Excel导入管理等")
@RequestParam(value = "operationModule", required = false) Integer operationModule,
@Parameter(description = "操作结果(成功/失败)")
@RequestParam(value = "operationResult", required = false) String operationResult,
@Parameter(description = "结果信息关键词(支持模糊搜索)")
@RequestParam(value = "keyword", required = false) String keyword,
HttpServletRequest httpServletRequest) {
try {
// 权限校验:仅管理员可以查看
User loginUser = userService.getLoginUser(httpServletRequest);
if (!userService.isAdmin(loginUser)) {
return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, "无权限查看操作历史");
}
log.info("获取操作历史,操作模块:{},操作结果:{},关键词:{}", operationModule, operationResult, keyword);
// 构建查询条件
QueryWrapper<OperationHistory> queryWrapper = new QueryWrapper<>();
// 添加操作模块筛选条件
if (operationModule != null && operationModule >= 0) {
queryWrapper.eq("operationModule", operationModule);
}
// 添加操作结果筛选条件
if (operationResult != null && !operationResult.trim().isEmpty()) {
if (!operationResult.equals("成功") && !operationResult.equals("失败")) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "操作结果只能是'成功'或'失败'");
}
queryWrapper.eq("operationResult", operationResult);
}
// 添加结果信息模糊搜索条件
if (keyword != null && !keyword.trim().isEmpty()) {
queryWrapper.like("resultMessage", keyword.trim());
}
// 按操作时间降序排序
queryWrapper.orderByDesc("operationTime");
// 查询操作历史
List<OperationHistory> records = operationHistoryService.list(queryWrapper);
log.info("操作历史查询成功,共{}条记录", records.size());
return ResultUtils.success(records);
} catch (Exception e) {
log.error("获取操作历史失败", e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取操作历史失败:" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,48 @@
package com.xy.xyaicpzs.controller;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.service.SmsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 短信控制器
*/
@RestController
@RequestMapping("/sms")
@Tag(name = "短信接口", description = "提供短信验证码相关功能")
public class SmsController {
@Autowired
private SmsService smsService;
/**
* 发送短信验证码
*
* @param phoneNumber 手机号
* @return 发送结果
*/
@PostMapping("/sendCode")
@Operation(summary = "发送短信验证码", description = "向指定手机号发送验证码每个手机号每天最多发送3次")
public ApiResponse<Boolean> sendVerificationCode(
@Parameter(description = "手机号码", required = true)
@RequestParam String phoneNumber) {
try {
boolean success = smsService.sendVerificationCode(phoneNumber);
if (success) {
return ResultUtils.success(true);
} else {
return ResultUtils.error(40001, "发送验证码失败,请稍后重试或联系客服");
}
} catch (Exception e) {
return ResultUtils.error(50000, "发送验证码异常:" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,83 @@
package com.xy.xyaicpzs.controller;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.util.SpeechRecognizerDemo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* 语音识别控制器
*/
@RestController
@RequestMapping("/api/speech")
public class SpeechRecognitionController {
@Autowired
private SpeechRecognizerDemo speechRecognizer;
/**
* 识别本地语音文件
* @param filePath 文件路径
* @param sampleRate 采样率
* @return 识别结果
*/
@GetMapping("/recognize")
public ApiResponse<Map<String, String>> recognizeSpeech(
@RequestParam("filePath") String filePath,
@RequestParam(value = "sampleRate", defaultValue = "16000") int sampleRate) {
String text = speechRecognizer.speechToText(filePath, sampleRate);
Map<String, String> result = new HashMap<>();
result.put("text", text);
return ResultUtils.success(result);
}
/**
* 上传并识别语音文件
* @param file 上传的语音文件
* @param sampleRate 采样率
* @return 识别结果
*/
@PostMapping("/upload-and-recognize")
public ApiResponse<Map<String, String>> uploadAndRecognize(
@RequestParam("file") MultipartFile file,
@RequestParam(value = "sampleRate", defaultValue = "16000") int sampleRate) {
if (file.isEmpty()) {
return ResultUtils.error(40001, "上传文件不能为空");
}
try {
// 创建临时目录
String tempDir = System.getProperty("java.io.tmpdir");
String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename();
Path filePath = Paths.get(tempDir, fileName);
// 保存上传的文件
file.transferTo(filePath.toFile());
// 识别语音
String text = speechRecognizer.speechToText(filePath.toString(), sampleRate);
// 删除临时文件
Files.deleteIfExists(filePath);
Map<String, String> result = new HashMap<>();
result.put("text", text);
return ResultUtils.success(result);
} catch (IOException e) {
return ResultUtils.error(50000, "文件处理失败:" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,578 @@
package com.xy.xyaicpzs.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xy.xyaicpzs.common.DeleteRequest;
import com.xy.xyaicpzs.common.ErrorCode;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.common.requset.VipCodeActivateRequest;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.domain.dto.user.*;
import com.xy.xyaicpzs.domain.entity.User;
import com.xy.xyaicpzs.domain.vo.UserVO;
import com.xy.xyaicpzs.exception.BusinessException;
import com.xy.xyaicpzs.service.UserService;
import com.xy.xyaicpzs.service.VipCodeService;
import com.xy.xyaicpzs.service.SmsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 用户接口
*/
@Slf4j
@RestController
@RequestMapping("/user")
@Tag(name = "用户管理", description = "用户管理相关接口")
public class UserController {
@Resource
private UserService userService;
@Resource
private VipCodeService vipCodeService;
@Resource
private SmsService smsService;
// region 登录相关
/**
* 用户登录
*
* @param userLoginRequest
* @param request
* @return
*/
@PostMapping("/login")
@Operation(summary = "用户登录", description = "用户登录接口")
public ApiResponse<UserVO> userLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) {
if (userLoginRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
String userAccount = userLoginRequest.getUserAccount();
String userPassword = userLoginRequest.getUserPassword();
if (StringUtils.isAnyBlank(userAccount, userPassword)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
User user = userService.userLogin(userAccount, userPassword, request);
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user, userVO);
return ResultUtils.success(userVO);
}
/**
* 用户注销
*
* @param request
* @return
*/
@PostMapping("/logout")
@Operation(summary = "用户注销", description = "用户注销接口")
public ApiResponse<Boolean> userLogout(HttpServletRequest request) {
if (request == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
boolean result = userService.userLogout(request);
return ResultUtils.success(result);
}
/**
* 获取当前登录用户
*
* @param request
* @return
*/
@GetMapping("/get/login")
@Operation(summary = "获取当前登录用户", description = "获取当前登录用户信息")
public ApiResponse<UserVO> getLoginUser(HttpServletRequest request) {
User user = userService.getLoginUser(request);
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user, userVO);
return ResultUtils.success(userVO);
}
// endregion
// region 增删改查
/**
* 创建用户
*
* @param userAddRequest
* @param request
* @return
*/
@PostMapping("/add")
@Operation(summary = "创建用户", description = "管理员创建用户")
public ApiResponse<Long> addUser(@RequestBody UserAddRequest userAddRequest, HttpServletRequest request) {
if (userAddRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
// 参数校验
String userAccount = userAddRequest.getUserAccount();
String userPassword = userAddRequest.getUserPassword();
String password = userAddRequest.getPassword();
String phone = userAddRequest.getPhone();
// 如果userPassword为空但password不为空则使用password
if (StringUtils.isBlank(userPassword) && StringUtils.isNotBlank(password)) {
userAddRequest.setUserPassword(password);
userPassword = password;
}
if (StringUtils.isAnyBlank(userAccount, userPassword)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号或密码不能为空");
}
if (phone != null && !phone.isEmpty()) {
// 如果提供了手机号,可以进行手机号格式校验
if (phone.length() != 11) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "手机号格式不正确");
}
}
User user = new User();
BeanUtils.copyProperties(userAddRequest, user);
// 密码加密使用Service层的加密方法
String encryptPassword = userService.encryptPassword(userPassword);
user.setUserPassword(encryptPassword);
boolean result = userService.save(user);
if (!result) {
throw new BusinessException(ErrorCode.OPERATION_ERROR);
}
return ResultUtils.success(user.getId());
}
/**
* 修改用户状态
*
* @param userStatusUpdateRequest 用户状态更新请求
* @param request HTTP请求
* @return 修改结果
*/
@PostMapping("/update-status")
@Operation(summary = "修改用户状态", description = "管理员修改用户状态(正常/封禁)")
public ApiResponse<Boolean> updateUserStatus(@RequestBody UserStatusUpdateRequest userStatusUpdateRequest,
HttpServletRequest request) {
if (userStatusUpdateRequest == null || userStatusUpdateRequest.getId() == null
|| userStatusUpdateRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户ID不正确");
}
Long id = userStatusUpdateRequest.getId();
Integer status = userStatusUpdateRequest.getStatus();
// 校验状态值
if (status == null || (status != 0 && status != 1)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "状态值不正确应为0(正常)或1(封禁)");
}
// 确认操作人员是否为管理员
User loginUser = userService.getLoginUser(request);
if (!userService.isAdmin(loginUser)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无管理员权限");
}
// 检查目标用户是否存在
User user = userService.getById(id);
if (user == null) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "用户不存在");
}
// 更新用户状态
user.setStatus(status);
boolean result = userService.updateById(user);
if (!result) {
throw new BusinessException(ErrorCode.OPERATION_ERROR, "操作失败");
}
return ResultUtils.success(true);
}
/**
* 删除用户
*
* @param deleteRequest
* @param request
* @return
*/
@PostMapping("/delete")
@Operation(summary = "删除用户", description = "管理员删除用户")
public ApiResponse<Boolean> deleteUser(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) {
if (deleteRequest == null || deleteRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
boolean b = userService.removeById(deleteRequest.getId());
return ResultUtils.success(b);
}
/**
* 更新用户
*
* @param userUpdateRequest
* @param request
* @return
*/
@PostMapping("/update")
@Operation(summary = "更新用户", description = "更新用户信息")
public ApiResponse<Boolean> updateUser(@RequestBody UserUpdateRequest userUpdateRequest, HttpServletRequest request) {
if (userUpdateRequest == null || userUpdateRequest.getId() == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
// 参数校验
String userPassword = userUpdateRequest.getUserPassword();
String password = userUpdateRequest.getPassword();
String phone = userUpdateRequest.getPhone();
// 如果userPassword为空但password不为空则使用password
if (StringUtils.isBlank(userPassword) && StringUtils.isNotBlank(password)) {
userUpdateRequest.setUserPassword(password);
userPassword = password;
}
if (phone != null && !phone.isEmpty()) {
// 如果提供了手机号,可以进行手机号格式校验
if (phone.length() != 11) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "手机号格式不正确");
}
}
User user = new User();
BeanUtils.copyProperties(userUpdateRequest, user);
// 如果更新了密码,需要进行加密
if (StringUtils.isNotBlank(userPassword)) {
String encryptPassword = userService.encryptPassword(userPassword);
user.setUserPassword(encryptPassword);
}
boolean result = userService.updateById(user);
return ResultUtils.success(result);
}
/**
* 根据 id 获取用户
*
* @param id
* @param request
* @return
*/
@GetMapping("/get")
@Operation(summary = "根据ID获取用户", description = "根据用户ID获取用户信息")
public ApiResponse<UserVO> getUserById(@RequestParam("id") long id, HttpServletRequest request) {
if (id <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
User user = userService.getById(id);
if (user == null) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
}
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user, userVO);
return ResultUtils.success(userVO);
}
/**
* 获取用户列表
*
* @param userQueryRequest
* @param request
* @return
*/
@GetMapping("/list")
@Operation(summary = "获取用户列表", description = "获取用户列表,支持用户名/手机号模糊匹配和角色状态筛选")
public ApiResponse<List<UserVO>> listUser(UserQueryRequest userQueryRequest, HttpServletRequest request) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (userQueryRequest != null) {
// 用户名模糊匹配
if (StringUtils.isNotBlank(userQueryRequest.getUserName())) {
queryWrapper.like("userName", userQueryRequest.getUserName());
}
// 手机号模糊匹配
if (StringUtils.isNotBlank(userQueryRequest.getPhone())) {
queryWrapper.like("phone", userQueryRequest.getPhone());
}
// 账号模糊匹配
if (StringUtils.isNotBlank(userQueryRequest.getUserAccount())) {
queryWrapper.like("userAccount", userQueryRequest.getUserAccount());
}
// 用户角色精确匹配
if (userQueryRequest.getUserRole() != null) {
queryWrapper.eq("userRole", userQueryRequest.getUserRole());
}
// 用户状态精确匹配
if (userQueryRequest.getStatus() != null) {
queryWrapper.eq("status", userQueryRequest.getStatus());
}
// 会员状态匹配
if (userQueryRequest.getIsVip() != null) {
queryWrapper.eq("isVip", userQueryRequest.getIsVip());
}
}
List<User> userList = userService.list(queryWrapper);
List<UserVO> userVOList = userList.stream().map(user -> {
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user, userVO);
return userVO;
}).collect(Collectors.toList());
return ResultUtils.success(userVOList);
}
/**
* 分页获取用户列表
*
* @param userQueryRequest
* @param request
* @return
*/
@GetMapping("/list/page")
@Operation(summary = "分页获取用户列表", description = "分页获取用户列表,支持用户名/手机号模糊匹配和角色状态筛选")
public ApiResponse<Page<UserVO>> listUserByPage(UserQueryRequest userQueryRequest, HttpServletRequest request) {
long current = 1;
long size = 10;
if (userQueryRequest != null) {
current = userQueryRequest.getCurrent();
size = userQueryRequest.getPageSize();
}
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (userQueryRequest != null) {
// 用户名模糊匹配
if (StringUtils.isNotBlank(userQueryRequest.getUserName())) {
queryWrapper.like("userName", userQueryRequest.getUserName());
}
// 手机号模糊匹配
if (StringUtils.isNotBlank(userQueryRequest.getPhone())) {
queryWrapper.like("phone", userQueryRequest.getPhone());
}
// 账号模糊匹配
if (StringUtils.isNotBlank(userQueryRequest.getUserAccount())) {
queryWrapper.like("userAccount", userQueryRequest.getUserAccount());
}
// 用户角色精确匹配
if (userQueryRequest.getUserRole() != null) {
queryWrapper.eq("userRole", userQueryRequest.getUserRole());
}
// 用户状态精确匹配
if (userQueryRequest.getStatus() != null) {
queryWrapper.eq("status", userQueryRequest.getStatus());
}
// 会员状态匹配
if (userQueryRequest.getIsVip() != null) {
queryWrapper.eq("isVip", userQueryRequest.getIsVip());
}
}
Page<User> userPage = userService.page(new Page<>(current, size), queryWrapper);
// 创建新的Page对象用于返回UserVO
Page<UserVO> userVOPage = new Page<>(userPage.getCurrent(), userPage.getSize(), userPage.getTotal());
List<UserVO> userVOList = userPage.getRecords().stream().map(user -> {
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user, userVO);
return userVO;
}).collect(Collectors.toList());
userVOPage.setRecords(userVOList);
return ResultUtils.success(userVOPage);
}
/**
* 获取用户统计信息
*
* @return 包含总用户数和会员数的统计信息
*/
@GetMapping("/count")
@Operation(summary = "获取用户统计信息", description = "获取系统中总用户数和会员数量")
public ApiResponse<Map<String, Long>> getUserCount() {
// 获取总用户数
long totalUserCount = userService.count();
// 获取会员数量(isVip=1)
QueryWrapper<User> vipQueryWrapper = new QueryWrapper<>();
vipQueryWrapper.eq("isVip", 1);
long vipUserCount = userService.count(vipQueryWrapper);
// 获取正常状态用户数量(status=0)
QueryWrapper<User> normalStatusWrapper = new QueryWrapper<>();
normalStatusWrapper.eq("status", 0);
long normalUserCount = userService.count(normalStatusWrapper);
// 获取封禁状态用户数量(status=1)
QueryWrapper<User> bannedStatusWrapper = new QueryWrapper<>();
bannedStatusWrapper.eq("status", 1);
long bannedUserCount = userService.count(bannedStatusWrapper);
// 构造返回结果
Map<String, Long> countMap = new HashMap<>();
countMap.put("totalUserCount", totalUserCount);
countMap.put("vipUserCount", vipUserCount);
countMap.put("normalUserCount", normalUserCount);
countMap.put("bannedUserCount", bannedUserCount);
return ResultUtils.success(countMap);
}
/**
* 激活会员码
*
* @param request 会员码激活请求
* @return 是否激活成功
*/
@PostMapping("/activate-vip")
@Operation(summary = "激活会员码", description = "用户使用会员码激活会员服务")
public ApiResponse<Boolean> activateVipCode(@RequestBody VipCodeActivateRequest request) {
if (request == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数不能为空");
}
if (request.getUserId() == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户ID不能为空");
}
if (StringUtils.isBlank(request.getCode())) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "会员码不能为空");
}
try {
boolean result = vipCodeService.activateVipCode(request.getUserId(), request.getCode());
return ResultUtils.success(result);
} catch (IllegalArgumentException e) {
log.error("会员码激活失败:{}", e.getMessage());
throw new BusinessException(ErrorCode.PARAMS_ERROR, e.getMessage());
} catch (RuntimeException e) {
log.error("会员码激活系统错误:{}", e.getMessage());
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "会员码激活失败,请稍后重试");
}
}
/**
* 手机号注册
*
* @param userPhoneRegisterRequest 手机号注册请求
* @return 用户ID
*/
@PostMapping("/phone/register")
@Operation(summary = "手机号注册", description = "使用手机号和验证码注册用户")
public ApiResponse<Long> userPhoneRegister(@RequestBody UserPhoneRegisterRequest userPhoneRegisterRequest) {
if (userPhoneRegisterRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
long result = userService.userPhoneRegister(userPhoneRegisterRequest);
return ResultUtils.success(result);
}
/**
* 手机号登录
*
* @param userPhoneLoginRequest 手机号登录请求
* @param request HTTP请求
* @return 用户信息
*/
@PostMapping("/phone/login")
@Operation(summary = "手机号登录", description = "使用手机号和验证码登录")
public ApiResponse<UserVO> userPhoneLogin(@RequestBody UserPhoneLoginRequest userPhoneLoginRequest, HttpServletRequest request) {
if (userPhoneLoginRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
User user = userService.userPhoneLogin(userPhoneLoginRequest, request);
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user, userVO);
return ResultUtils.success(userVO);
}
/**
* 重置密码
*
* @param resetPasswordRequest 重置密码请求
* @return 是否重置成功
*/
@PostMapping("/reset-password")
@Operation(summary = "重置密码", description = "使用手机号和验证码重置密码")
public ApiResponse<Boolean> resetPassword(@RequestBody ResetPasswordRequest resetPasswordRequest) {
if (resetPasswordRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数不能为空");
}
String phone = resetPasswordRequest.getPhone();
String code = resetPasswordRequest.getCode();
String newPassword = resetPasswordRequest.getNewPassword();
String confirmPassword = resetPasswordRequest.getConfirmPassword();
// 校验参数
if (StringUtils.isAnyBlank(phone, code, newPassword, confirmPassword)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不能为空");
}
// 校验手机号格式
if (phone.length() != 11) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "手机号格式不正确");
}
// 校验两次密码是否一致
if (!newPassword.equals(confirmPassword)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致");
}
// 密码长度校验
if (newPassword.length() < 8) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度不能小于8位");
}
// 验证短信验证码
boolean isCodeValid = smsService.verifyCode(phone, code);
if (!isCodeValid) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "验证码错误或已过期");
}
// 查询用户是否存在
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("phone", phone);
User user = userService.getOne(queryWrapper);
if (user == null) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "未找到该手机号注册的用户");
}
// 更新密码
String encryptPassword = userService.encryptPassword(newPassword);
user.setUserPassword(encryptPassword);
boolean result = userService.updateById(user);
if (!result) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "密码重置失败,请稍后重试");
}
ApiResponse<Boolean> response = ResultUtils.success(true);
response.setMessage("密码重置成功");
return response;
}
// endregion
}

View File

@@ -0,0 +1,298 @@
package com.xy.xyaicpzs.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xy.xyaicpzs.common.ErrorCode;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.common.requset.GenerateVipCodesRequest;
import com.xy.xyaicpzs.common.requset.VipCodeQueryRequest;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.domain.entity.User;
import com.xy.xyaicpzs.domain.entity.VipCode;
import com.xy.xyaicpzs.domain.vo.VipCodeVO;
import com.xy.xyaicpzs.exception.BusinessException;
import com.xy.xyaicpzs.service.OperationHistoryService;
import com.xy.xyaicpzs.service.UserService;
import com.xy.xyaicpzs.service.VipCodeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 会员码管理接口
*/
@Slf4j
@RestController
@RequestMapping("/vip-code")
@Tag(name = "会员码管理", description = "会员码管理相关接口")
public class VipCodeController {
@Resource
private VipCodeService vipCodeService;
@Autowired
private UserService userService;
@Autowired
private OperationHistoryService operationHistoryService;
/**
* 批量生成会员码
*
* @param request 生成会员码请求
* @return 生成成功的数量
*/
@PostMapping("/generate")
@Operation(summary = "批量生成会员码", description = "管理员批量生成会员码")
public ApiResponse<Integer> generateVipCodes(@RequestBody GenerateVipCodesRequest request,
HttpServletRequest httpServletRequest) {
User loginUser = userService.getLoginUser(httpServletRequest);
if (!userService.isAdmin(loginUser)){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "无权限");
}
Long userId = loginUser.getId();
String userName = loginUser.getUserName();
if (request == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数不能为空");
}
if (request.getNumCodes() == null || request.getNumCodes() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "生成数量必须大于0");
}
if (request.getVipExpireTime() == null || request.getVipExpireTime() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "会员有效月数必须大于0");
}
if (request.getNumCodes() > 1000) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "单次生成数量不能超过1000");
}
try {
int result = vipCodeService.generateVipCodes(request.getNumCodes(), request.getVipExpireTime(), userId, userName);
// 记录操作历史 - 成功
String resultMessage = String.format("%s成功生成%d个会员码有效月数%d", userName, result, request.getVipExpireTime());
operationHistoryService.recordOperation(userId, "批量生成会员码", 0, "成功", resultMessage);
return ResultUtils.success(result);
} catch (IllegalArgumentException e) {
log.error("生成会员码参数错误:{}", e.getMessage());
// 记录操作历史 - 失败
String resultMessage = String.format("%s生成会员码失败%s请求数量%d有效月数%d",
userName, e.getMessage(), request.getNumCodes(), request.getVipExpireTime());
operationHistoryService.recordOperation(userId, "批量生成会员码", 0, "失败", resultMessage);
throw new BusinessException(ErrorCode.PARAMS_ERROR, e.getMessage());
} catch (RuntimeException e) {
log.error("生成会员码系统错误:{}", e.getMessage());
// 记录操作历史 - 失败
String resultMessage = String.format("%s生成会员码系统错误%s请求数量%d有效月数%d",
userName, e.getMessage(), request.getNumCodes(), request.getVipExpireTime());
operationHistoryService.recordOperation(userId, "批量生成会员码", 0, "失败", resultMessage);
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "生成会员码失败,请稍后重试");
}
}
/**
* 获取一个可用的会员码
*
* @param vipExpireTime 会员有效月数1或12
* @return 可用的会员码
*/
@GetMapping("/available")
@Operation(summary = "获取可用会员码", description = "根据有效月数获取一个可用的会员码")
public ApiResponse<String> getAvailableVipCode(@RequestParam("vipExpireTime") Integer vipExpireTime,
HttpServletRequest httpServletRequest) {
User loginUser = userService.getLoginUser(httpServletRequest);
if (!userService.isAdmin(loginUser)){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "无权限");
}
Long userId = loginUser.getId();
String userName = loginUser.getUserName();
if (vipExpireTime == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "会员有效月数不能为空");
}
if (vipExpireTime != 1 && vipExpireTime != 12) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "会员有效月数只能是1或12");
}
try {
String code = vipCodeService.getAvailableVipCode(vipExpireTime, userId, userName);
if (code == null) {
// 记录操作历史 - 失败
String resultMessage = String.format("%s获取可用会员码失败没有找到可用的会员码有效月数%d", userName, vipExpireTime);
operationHistoryService.recordOperation(userId, "获取可用会员码", 0, "失败", resultMessage);
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "没有找到可用的会员码");
}
// 记录操作历史 - 成功
String resultMessage = String.format("%s成功获取可用会员码%s有效月数%d", userName, code, vipExpireTime);
operationHistoryService.recordOperation(userId, "获取可用会员码", 0, "成功", resultMessage);
return ResultUtils.success(code);
} catch (IllegalArgumentException e) {
log.error("获取可用会员码参数错误:{}", e.getMessage());
// 记录操作历史 - 失败
String resultMessage = String.format("%s获取可用会员码参数错误%s有效月数%d", userName, e.getMessage(), vipExpireTime);
operationHistoryService.recordOperation(userId, "获取可用会员码", 0, "失败", resultMessage);
throw new BusinessException(ErrorCode.PARAMS_ERROR, e.getMessage());
} catch (Exception e) {
log.error("获取可用会员码系统错误:{}", e.getMessage());
// 记录操作历史 - 失败
String resultMessage = String.format("%s获取可用会员码系统错误%s有效月数%d", userName, e.getMessage(), vipExpireTime);
operationHistoryService.recordOperation(userId, "获取可用会员码", 0, "失败", resultMessage);
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "获取可用会员码失败,请生成后获取。");
}
}
/**
* 分页获取会员码列表
*
* @param vipCodeQueryRequest 会员码查询请求
* @param httpServletRequest Http请求
* @return 分页会员码列表
*/
@GetMapping("/list/page")
@Operation(summary = "分页获取会员码列表", description = "分页获取会员码列表,支持根据会员码、使用状态和时间筛选")
public ApiResponse<Page<VipCodeVO>> listVipCodesByPage(VipCodeQueryRequest vipCodeQueryRequest,
HttpServletRequest httpServletRequest) {
// 权限校验
User loginUser = userService.getLoginUser(httpServletRequest);
if (!userService.isAdmin(loginUser)){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "无权限");
}
if (vipCodeQueryRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数不能为空");
}
long current = vipCodeQueryRequest.getCurrent();
long pageSize = vipCodeQueryRequest.getPageSize();
// 构建查询条件
QueryWrapper<VipCode> queryWrapper = new QueryWrapper<>();
// 根据会员码模糊查询
if (StringUtils.isNotBlank(vipCodeQueryRequest.getCode())) {
queryWrapper.like("code", vipCodeQueryRequest.getCode());
}
// 根据使用状态筛选
if (vipCodeQueryRequest.getIsUse() != null) {
queryWrapper.eq("isUse", vipCodeQueryRequest.getIsUse());
}
// 根据会员有效月数筛选
if (vipCodeQueryRequest.getVipExpireTime() != null) {
queryWrapper.eq("vipExpireTime", vipCodeQueryRequest.getVipExpireTime());
}
// 根据创建人ID筛选
if (vipCodeQueryRequest.getCreatedUserId() != null) {
queryWrapper.eq("createdUserId", vipCodeQueryRequest.getCreatedUserId());
}
// 根据创建人名称模糊查询
if (StringUtils.isNotBlank(vipCodeQueryRequest.getCreatedUserName())) {
queryWrapper.like("createdUserName", vipCodeQueryRequest.getCreatedUserName());
}
// 根据使用人ID筛选
if (vipCodeQueryRequest.getUsedUserId() != null) {
queryWrapper.eq("usedUserId", vipCodeQueryRequest.getUsedUserId());
}
// 根据创建时间范围筛选
if (vipCodeQueryRequest.getStartTime() != null && vipCodeQueryRequest.getEndTime() != null) {
queryWrapper.between("createTime", vipCodeQueryRequest.getStartTime(), vipCodeQueryRequest.getEndTime());
} else if (vipCodeQueryRequest.getStartTime() != null) {
queryWrapper.ge("createTime", vipCodeQueryRequest.getStartTime());
} else if (vipCodeQueryRequest.getEndTime() != null) {
queryWrapper.le("createTime", vipCodeQueryRequest.getEndTime());
}
// 按会员编号升序排序(从小到大)
queryWrapper.orderByAsc("vipNumber");
// 执行分页查询
Page<VipCode> vipCodePage = vipCodeService.page(new Page<>(current, pageSize), queryWrapper);
// 转换为VO对象
List<VipCodeVO> vipCodeVOList = vipCodePage.getRecords().stream().map(vipCode -> {
VipCodeVO vipCodeVO = new VipCodeVO();
BeanUtils.copyProperties(vipCode, vipCodeVO);
return vipCodeVO;
}).collect(Collectors.toList());
// 创建VO分页对象确保正确传递所有分页信息
Page<VipCodeVO> vipCodeVOPage = new Page<>(vipCodePage.getCurrent(), vipCodePage.getSize(), vipCodePage.getTotal());
vipCodeVOPage.setRecords(vipCodeVOList);
// 手动设置pages值
vipCodeVOPage.setPages(vipCodePage.getPages());
return ResultUtils.success(vipCodeVOPage);
}
/**
* 获取会员码统计数量
*
* @param httpServletRequest Http请求
* @return 会员码统计数量
*/
@GetMapping("/count")
@Operation(summary = "获取会员码统计数量", description = "获取系统中会员码总数、可用会员码和已使用会员码的数量")
public ApiResponse<Map<String, Long>> getVipCodeCount(HttpServletRequest httpServletRequest) {
// 权限校验
User loginUser = userService.getLoginUser(httpServletRequest);
if (!userService.isAdmin(loginUser)){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "无权限");
}
try {
// 构建查询条件 - 总数
long totalCount = vipCodeService.count();
// 构建查询条件 - 已使用的会员码
QueryWrapper<VipCode> usedQueryWrapper = new QueryWrapper<>();
usedQueryWrapper.eq("isUse", 1);
long usedCount = vipCodeService.count(usedQueryWrapper);
// 构建查询条件 - 可用的会员码
QueryWrapper<VipCode> availableQueryWrapper = new QueryWrapper<>();
availableQueryWrapper.eq("isUse", 0);
long availableCount = vipCodeService.count(availableQueryWrapper);
// 构造返回结果
Map<String, Long> countMap = new HashMap<>();
countMap.put("totalCount", totalCount);
countMap.put("availableCount", availableCount);
countMap.put("usedCount", usedCount);
return ResultUtils.success(countMap);
} catch (Exception e) {
log.error("获取会员码统计数量失败:{}", e.getMessage());
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "获取会员码统计数量失败,请稍后重试");
}
}
}

View File

@@ -0,0 +1,139 @@
package com.xy.xyaicpzs.controller;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.common.ErrorCode;
import com.xy.xyaicpzs.domain.entity.VipExchangeRecord;
import com.xy.xyaicpzs.service.VipExchangeRecordService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* VIP兑换记录控制器
*/
@RestController
@RequestMapping("/vip-exchange-record")
@Slf4j
@Tag(name = "VIP兑换记录管理", description = "VIP兑换记录相关接口")
public class VipExchangeRecordController {
@Autowired
private VipExchangeRecordService vipExchangeRecordService;
/**
* 根据用户ID获取所有兑换记录
*/
@GetMapping("/user/{userId}")
@Operation(summary = "获取用户兑换记录", description = "根据用户ID获取该用户的所有VIP兑换记录")
public ApiResponse<List<VipExchangeRecord>> getExchangeRecordsByUserId(
@Parameter(description = "用户ID", required = true)
@PathVariable("userId") Long userId) {
try {
log.info("获取用户兑换记录用户ID{}", userId);
// 参数校验
if (userId == null || userId <= 0) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "用户ID不能为空且必须大于0");
}
// 查询用户兑换记录
List<VipExchangeRecord> records = vipExchangeRecordService.getExchangeRecordsByUserId(userId);
log.info("用户ID{} 的兑换记录查询成功,共{}条记录", userId, records.size());
return ResultUtils.success(records);
} catch (Exception e) {
log.error("获取用户兑换记录失败用户ID{}", userId, e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取兑换记录失败:" + e.getMessage());
}
}
/**
* 根据用户ID获取兑换记录带分页
*/
@GetMapping("/user/{userId}/page")
@Operation(summary = "分页获取用户兑换记录", description = "根据用户ID分页获取该用户的VIP兑换记录")
public ApiResponse<List<VipExchangeRecord>> getExchangeRecordsByUserIdWithPage(
@Parameter(description = "用户ID", required = true)
@PathVariable("userId") Long userId,
@Parameter(description = "页码从1开始", required = false)
@RequestParam(value = "page", defaultValue = "1") Integer page,
@Parameter(description = "每页大小", required = false)
@RequestParam(value = "size", defaultValue = "10") Integer size) {
try {
log.info("分页获取用户兑换记录用户ID{},页码:{},每页大小:{}", userId, page, size);
// 参数校验
if (userId == null || userId <= 0) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "用户ID不能为空且必须大于0");
}
if (page < 1) {
page = 1;
}
if (size < 1 || size > 100) {
size = 10;
}
// 查询用户兑换记录
List<VipExchangeRecord> allRecords = vipExchangeRecordService.getExchangeRecordsByUserId(userId);
// 手动分页
int start = (page - 1) * size;
int end = Math.min(start + size, allRecords.size());
List<VipExchangeRecord> pageRecords = allRecords.subList(start, end);
log.info("用户ID{} 的兑换记录分页查询成功,总记录数:{},当前页记录数:{}",
userId, allRecords.size(), pageRecords.size());
return ResultUtils.success(pageRecords);
} catch (Exception e) {
log.error("分页获取用户兑换记录失败用户ID{}", userId, e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取兑换记录失败:" + e.getMessage());
}
}
/**
* 根据兑换记录ID获取详情
*/
@GetMapping("/{recordId}")
@Operation(summary = "获取兑换记录详情", description = "根据兑换记录ID获取详细信息")
public ApiResponse<VipExchangeRecord> getExchangeRecordById(
@Parameter(description = "兑换记录ID", required = true)
@PathVariable("recordId") Long recordId) {
try {
log.info("获取兑换记录详情记录ID{}", recordId);
// 参数校验
if (recordId == null || recordId <= 0) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "兑换记录ID不能为空且必须大于0");
}
// 查询兑换记录
VipExchangeRecord record = vipExchangeRecordService.getById(recordId);
if (record == null) {
return ResultUtils.error(ErrorCode.NOT_FOUND_ERROR, "兑换记录不存在");
}
log.info("兑换记录详情查询成功记录ID{}", recordId);
return ResultUtils.success(record);
} catch (Exception e) {
log.error("获取兑换记录详情失败记录ID{}", recordId, e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取兑换记录详情失败:" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,34 @@
package com.xy.xyaicpzs.domain.dto.user;
import lombok.Data;
import java.io.Serializable;
/**
* 重置密码请求
*/
@Data
public class ResetPasswordRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户手机号
*/
private String phone;
/**
* 短信验证码
*/
private String code;
/**
* 新密码
*/
private String newPassword;
/**
* 确认新密码
*/
private String confirmPassword;
}

View File

@@ -0,0 +1,70 @@
package com.xy.xyaicpzs.domain.dto.user;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 用户创建请求
*/
@Data
public class UserAddRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户昵称
*/
private String userName;
/**
* 账号
*/
private String userAccount;
/**
* 电话
*/
private String phone;
/**
* 用户头像
*/
private String userAvatar;
/**
* 性别
*/
private Integer gender;
/**
* 用户角色user / admin
*/
private String userRole;
/**
* 密码
*/
private String userPassword;
/**
* 密码(兼容格式)
*/
private String password;
/**
* 是否会员0-非会员1-会员
*/
private Integer isVip;
/**
* 会员到期时间
*/
private Date vipExpire;
/**
* 状态0-正常1-封禁
*/
private Integer status;
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.domain.dto.user;
import lombok.Data;
import java.io.Serializable;
/**
* 用户登录请求
*/
@Data
public class UserLoginRequest implements Serializable {
private static final long serialVersionUID = 3191241716373120793L;
private String userAccount;
private String userPassword;
}

View File

@@ -0,0 +1,22 @@
package com.xy.xyaicpzs.domain.dto.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 用户手机号登录请求
*/
@Data
@Schema(description = "用户手机号登录请求")
public class UserPhoneLoginRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "手机号")
private String phone;
@Schema(description = "验证码")
private String code;
}

View File

@@ -0,0 +1,34 @@
package com.xy.xyaicpzs.domain.dto.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 用户手机号注册请求
*/
@Data
@Schema(description = "用户手机号注册请求")
public class UserPhoneRegisterRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "用户账号")
private String userAccount;
@Schema(description = "用户名称")
private String userName;
@Schema(description = "用户密码")
private String userPassword;
@Schema(description = "确认密码")
private String checkPassword;
@Schema(description = "手机号")
private String phone;
@Schema(description = "验证码")
private String code;
}

View File

@@ -0,0 +1,57 @@
package com.xy.xyaicpzs.domain.dto.user;
import com.xy.xyaicpzs.common.PageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* 用户查询请求
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class UserQueryRequest extends PageRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
private Long id;
/**
* 用户昵称
*/
private String userName;
/**
* 账号
*/
private String userAccount;
/**
* 手机号
*/
private String phone;
/**
* 性别
*/
private Integer gender;
/**
* 用户角色user / admin
*/
private String userRole;
/**
* 是否会员0-非会员1-会员
*/
private Integer isVip;
/**
* 状态0-正常1-封禁
*/
private Integer status;
}

View File

@@ -0,0 +1,22 @@
package com.xy.xyaicpzs.domain.dto.user;
import lombok.Data;
import java.io.Serializable;
/**
* 用户注册请求
*/
@Data
public class UserRegisterRequest implements Serializable {
private static final long serialVersionUID = 3191241716373120793L;
private String userAccount;
private String userName;
private String userPassword;
private String checkPassword;
}

View File

@@ -0,0 +1,24 @@
package com.xy.xyaicpzs.domain.dto.user;
import lombok.Data;
import java.io.Serializable;
/**
* 用户状态更新请求
*/
@Data
public class UserStatusUpdateRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户id
*/
private Long id;
/**
* 状态0-正常1-封禁
*/
private Integer status;
}

View File

@@ -0,0 +1,75 @@
package com.xy.xyaicpzs.domain.dto.user;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 用户更新请求
*/
@Data
public class UserUpdateRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
private Long id;
/**
* 用户昵称
*/
private String userName;
/**
* 账号
*/
private String userAccount;
/**
* 电话
*/
private String phone;
/**
* 用户头像
*/
private String userAvatar;
/**
* 性别
*/
private Integer gender;
/**
* 用户角色user / admin
*/
private String userRole;
/**
* 密码
*/
private String userPassword;
/**
* 密码(兼容格式)
*/
private String password;
/**
* 是否会员0-非会员1-会员
*/
private Integer isVip;
/**
* 会员到期时间
*/
private Date vipExpire;
/**
* 状态0-正常1-封禁
*/
private Integer status;
}

View File

@@ -0,0 +1,50 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 蓝球最近100期数据表
* @TableName blue_history_100
*/
@TableName(value ="blue_history_100")
@Data
public class BlueHistory100 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 球号
*/
private Integer ballNumber;
/**
* 出现频次
*/
private Integer frequencyCount;
/**
* 平均隐现期(次)
*/
private Double averageInterval;
/**
* 当前隐现期(次)
*/
private Integer nowInterval;
/**
* 最多连出期(次)
*/
private Integer maxConsecutiveCount;
/**
* 点系数
*/
private Double pointCoefficient;
}

View File

@@ -0,0 +1,55 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 蓝球全部历史数据表
* @TableName blue_history_all
*/
@TableName(value ="blue_history_all")
@Data
public class BlueHistoryAll {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 球号
*/
private Integer ballNumber;
/**
* 出现频次
*/
private Integer frequencyCount;
/**
* 出现频率百分比
*/
private Double frequencyPercentage;
/**
* 平均隐现期(次)
*/
private Double averageInterval;
/**
* 最长隐现期(次)
*/
private Integer maxHiddenInterval;
/**
* 最多连出期(次)
*/
private Integer maxConsecutiveCount;
/**
* 点系数
*/
private Double pointCoefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 蓝球历史数据排行表
* @TableName blue_history_top
*/
@TableName(value ="blue_history_top")
@Data
public class BlueHistoryTop {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 排行
*/
private Integer no;
/**
* 球号
*/
private Integer ballNumber;
/**
* 点系数
*/
private Double pointCoefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 创建蓝球100期数据排行表
* @TableName blue_history_top_100
*/
@TableName(value ="blue_history_top_100")
@Data
public class BlueHistoryTop100 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 排行
*/
private Integer no;
/**
* 球号
*/
private Integer ballNumber;
/**
* 点系数
*/
private Double pointCoefficient;
}

View File

@@ -0,0 +1,56 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import lombok.Data;
/**
* 聊天消息表
* @TableName chat_message
*/
@TableName(value ="chat_message")
@Data
public class ChatMessage {
/**
* id
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 会话ID
*/
private String conversationId;
/**
* 用户ID关联用户表
*/
private String studentId;
/**
* 消息类型(如: 用户提问、AI回答
*/
private String messageType;
/**
* 消息内容
*/
private String content;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 是否删除 0-未删除 1-已删除
*/
private Integer isDelete;
}

View File

@@ -0,0 +1,50 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 最近100期数据表
* @TableName history_100
*/
@TableName(value ="history_100")
@Data
public class History100 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 球号
*/
private Integer ballNumber;
/**
* 出现频次
*/
private Integer frequencyCount;
/**
* 平均隐现期(次)
*/
private Double averageInterval;
/**
* 当前隐现期(次)
*/
private Integer nowInterval;
/**
* 最多连出期(次)
*/
private Integer maxConsecutiveCount;
/**
* 点系数
*/
private Double pointCoefficient;
}

View File

@@ -0,0 +1,55 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 历史数据表
* @TableName history_all
*/
@TableName(value ="history_all")
@Data
public class HistoryAll {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 球号
*/
private Integer ballNumber;
/**
* 出现频次
*/
private Integer frequencyCount;
/**
* 出现频率百分比
*/
private Double frequencyPercentage;
/**
* 平均间隔
*/
private Double averageInterval;
/**
* 最长隐藏间隔
*/
private Integer maxHiddenInterval;
/**
* 最大连续出现次数
*/
private Integer maxConsecutiveCount;
/**
* 点系数
*/
private Double pointCoefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 历史数据排行表
* @TableName history_top
*/
@TableName(value ="history_top")
@Data
public class HistoryTop {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 排行
*/
private Integer no;
/**
* 球号
*/
private Integer ballNumber;
/**
* 点系数
*/
private Double pointCoefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 创建100期数据排行表
* @TableName history_top_100
*/
@TableName(value ="history_top_100")
@Data
public class HistoryTop100 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 排行
*/
private Integer no;
/**
* 球号
*/
private Integer ballNumber;
/**
* 点系数
*/
private Double pointCoefficient;
}

View File

@@ -0,0 +1,60 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import lombok.Data;
/**
* 彩票开奖信息表
* @TableName lottery_draws
*/
@TableName(value ="lottery_draws")
@Data
public class LotteryDraws {
/**
* 开奖期号
*/
@TableId
private Long drawId;
/**
* 开奖日期
*/
private Date drawDate;
/**
* 红1
*/
private Integer redBall1;
/**
* 红2
*/
private Integer redBall2;
/**
* 红3
*/
private Integer redBall3;
/**
* 红4
*/
private Integer redBall4;
/**
* 红5
*/
private Integer redBall5;
/**
* 红6
*/
private Integer redBall6;
/**
* 蓝球
*/
private Integer blueBall;
}

View File

@@ -0,0 +1,56 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import lombok.Data;
/**
* 操作历史记录表
* @TableName operation_history
*/
@TableName(value ="operation_history")
@Data
public class OperationHistory {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 操作用户ID
*/
private Long userId;
/**
* 操作类型(批量生成会员码/获取可用会员码/Excel导入等
*/
private String operationType;
/**
* 操作模块(会员码管理/Excel导入管理等
*/
private Integer operationModule;
/**
* 操作结果(成功/失败)
*/
private String operationResult;
/**
* 结果消息
*/
private String resultMessage;
/**
* 操作时间
*/
private Date operationTime;
/**
* 更新时间
*/
private Date updateTime;
}

View File

@@ -0,0 +1,96 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import lombok.Data;
/**
* 彩票开奖信息表
* @TableName predict_record
*/
@TableName(value ="predict_record")
@Data
public class PredictRecord {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 开奖期号
*/
private Long drawId;
/**
* 开奖日期
*/
private Date drawDate;
/**
* 红1
*/
private Integer redBall1;
/**
* 红2
*/
private Integer redBall2;
/**
* 红3
*/
private Integer redBall3;
/**
* 红4
*/
private Integer redBall4;
/**
* 红5
*/
private Integer redBall5;
/**
* 红6
*/
private Integer redBall6;
/**
* 蓝球
*/
private Integer blueBall;
/**
* 预测状态(待开奖/已开奖)
*/
private String predictStatus;
/**
* 预测结果(未中奖/三等奖/二等奖/一等奖)
*/
private String predictResult;
/**
* 预测时间
*/
private Date predictTime;
/**
* 奖金
*/
private Long bonus;
/**
* 类型
*/
private String type;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* t11表蓝球组红球的面系数
* @TableName t11
*/
@TableName(value ="t11")
@Data
public class T11 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 主球
*/
private Integer masterBallNumber;
/**
* 从球
*/
private Integer slaveBallNumber;
/**
* 面系数
*/
private Double faceCoefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* t3表红球组红球的线系数
* @TableName t3
*/
@TableName(value ="t3")
@Data
public class T3 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 主球
*/
private Integer masterBallNumber;
/**
* 从球
*/
private Integer slaveBallNumber;
/**
* 线系数
*/
private Double lineCoefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* t4表蓝球组红球的线系数
* @TableName t4
*/
@TableName(value ="t4")
@Data
public class T4 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 主球
*/
private Integer masterBallNumber;
/**
* 从球
*/
private Integer slaveBallNumber;
/**
* 线系数
*/
private Double lineCoefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* t5表蓝球组蓝球的线系数
* @TableName t5
*/
@TableName(value ="t5")
@Data
public class T5 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 主球
*/
private Integer masterBallNumber;
/**
* 从球
*/
private Integer slaveBallNumber;
/**
* 线系数
*/
private Double lineCoefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* t6表红球组蓝球的线系数
* @TableName t6
*/
@TableName(value ="t6")
@Data
public class T6 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 主球
*/
private Integer masterBallNumber;
/**
* 从球
*/
private Integer slaveBallNumber;
/**
* 线系数
*/
private Double lineCoefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* t7表红球组红球的面系数
* @TableName t7
*/
@TableName(value ="t7")
@Data
public class T7 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 主球
*/
private Integer masterBallNumber;
/**
* 从球
*/
private Integer slaveBallNumber;
/**
* 面系数
*/
private Double faceCoefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* t8表红球组蓝球的面系数
* @TableName t8
*/
@TableName(value ="t8")
@Data
public class T8 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 主球
*/
private Integer masterBallNumber;
/**
* 从球
*/
private Integer slaveBallNumber;
/**
* 面系数
*/
private Double faceCoefficient;
}

View File

@@ -0,0 +1,86 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import lombok.Data;
/**
* 用户
* @TableName user
*/
@TableName(value ="user")
@Data
public class User {
/**
* id
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 用户昵称
*/
private String userName;
/**
* 账号
*/
private String userAccount;
/**
* 电话
*/
private String phone;
/**
* 用户头像
*/
private String userAvatar;
/**
* 性别
*/
private Integer gender;
/**
* 用户角色user / admin
*/
private String userRole;
/**
* 密码
*/
private String userPassword;
/**
* 是否会员0-非会员1-会员
*/
private Integer isVip;
/**
* 会员到期时间
*/
private Date vipExpire;
/**
* 状态0-正常1-封禁
*/
private Integer status;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 是否删除
*/
private Integer isDelete;
}

View File

@@ -0,0 +1,72 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import lombok.Data;
/**
* 会员码表
* @TableName vip_code
*/
@TableName(value ="vip_code")
@Data
public class VipCode {
/**
* 主键
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 会员码
*/
private String code;
/**
* 会员有效月数(1/12)
*/
private Integer vipExpireTime;
/**
* 会员编号
*/
private Integer vipNumber;
/**
* 是否使用0-未使用1-已使用
*/
private Integer isUse;
/**
* 创建的用户id
*/
private Long createdUserId;
/**
* 创建的用户名称
*/
private String createdUserName;
/**
* 使用的用户id
*/
private Long usedUserId;
/**
* 使用的用户名称
*/
private String usedUserName;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}

View File

@@ -0,0 +1,61 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import lombok.Data;
/**
* 会员兑换表
* @TableName vip_exchange_record
*/
@TableName(value ="vip_exchange_record")
@Data
public class VipExchangeRecord {
/**
* 唯一标识符
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 月度会员/年度会员
*/
private String type;
/**
* 兑换方式
*/
private Integer exchangeMode;
/**
* 订单编号
*/
private Long orderNo;
/**
* 订单金额
*/
private Integer orderAmount;
/**
* 是否兑换(未兑换/已兑换)
*/
private Integer isUse;
/**
* 兑换时间
*/
private Date exchangeTime;
/**
* 更新时间
*/
private Date updateTime;
}

View File

@@ -0,0 +1,39 @@
package com.xy.xyaicpzs.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 球号组合分析结果VO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "球号组合分析结果")
public class BallCombinationAnalysisVO {
@Schema(description = "当前两个球的组合系数")
private Double faceCoefficient;
@Schema(description = "与主球组合系数最高的球号")
private Integer highestBall;
@Schema(description = "与主球组合系数最高的值")
private Double highestCoefficient;
@Schema(description = "与主球组合系数最低的球号")
private Integer lowestBall;
@Schema(description = "与主球组合系数最低的值")
private Double lowestCoefficient;
@Schema(description = "与主球组合的所有系数平均值")
private Double averageCoefficient;
@Schema(description = "最新开奖期号")
private Long latestDrawId;
}

View File

@@ -0,0 +1,27 @@
package com.xy.xyaicpzs.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 球号命中率统计VO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "球号命中率统计")
public class BallHitRateVO {
@Schema(description = "命中次数")
private Integer hitCount;
@Schema(description = "总次数")
private Integer totalCount;
@Schema(description = "命中率(百分比)")
private Double hitRate;
}

View File

@@ -0,0 +1,39 @@
package com.xy.xyaicpzs.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 球号持续性分析结果VO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "球号持续性分析结果")
public class BallPersistenceAnalysisVO {
@Schema(description = "当前两个球的组合线系数")
private Double lineCoefficient;
@Schema(description = "与主球组合线系数最高的球号")
private Integer highestBall;
@Schema(description = "与主球组合线系数最高的值")
private Double highestCoefficient;
@Schema(description = "与主球组合线系数最低的球号")
private Integer lowestBall;
@Schema(description = "与主球组合线系数最低的值")
private Double lowestCoefficient;
@Schema(description = "与主球组合的所有线系数平均值")
private Double averageCoefficient;
@Schema(description = "最新开奖期号")
private Long latestDrawId;
}

View File

@@ -0,0 +1,47 @@
package com.xy.xyaicpzs.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.List;
/**
* 奖金估算VO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "奖金估算信息")
public class PrizeEstimateVO {
@Schema(description = "总奖金合计")
private BigDecimal totalPrize;
@Schema(description = "奖项明细")
private List<PrizeDetailItem> prizeDetails;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "奖项明细项")
public static class PrizeDetailItem {
@Schema(description = "中奖等级,例如:一等奖、二等奖等")
private String prizeLevel;
@Schema(description = "中奖注数")
private Integer winningCount;
@Schema(description = "单注奖金(元)")
private BigDecimal singlePrize;
@Schema(description = "该等级奖金小计(元)")
private BigDecimal subtotal;
}
}

View File

@@ -0,0 +1,27 @@
package com.xy.xyaicpzs.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 红球命中率统计VO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "红球命中率统计")
public class RedBallHitRateVO {
@Schema(description = "命中总红球数")
private Integer totalHitCount;
@Schema(description = "总预测红球数")
private Integer totalPredictedCount;
@Schema(description = "红球命中率(百分比)")
private Double hitRate;
}

View File

@@ -0,0 +1,46 @@
package com.xy.xyaicpzs.domain.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.math.BigDecimal;
/**
* 用户预测统计数据VO
*/
@Data
public class UserPredictStatVO {
/**
* 用户ID
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long userId;
/**
* 预测次数(总记录数)
*/
private Long predictCount;
/**
* 待开奖次数
*/
private Long pendingCount;
/**
* 命中次数
*/
private Long hitCount;
/**
* 命中率保留4位小数
*/
private BigDecimal hitRate;
/**
* 已开奖次数(总次数 - 待开奖次数)
*/
private Long drawnCount;
}

View File

@@ -0,0 +1,78 @@
package com.xy.xyaicpzs.domain.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 用户视图(脱敏)
*/
@Data
public class UserVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/**
* 用户昵称
*/
private String userName;
/**
* 用户昵称
*/
private String userAccount;
/**
* 用户头像
*/
private String userAvatar;
/**
* 性别
*/
private Integer gender;
/**
* 用户角色user / admin
*/
private String userRole;
/**
* 用户角色user / admin
*/
private String phone;
/**
* 是否会员0-非会员1-会员
*/
private Integer isVip;
/**
* 会员到期时间
*/
private Date vipExpire;
/**
* 状态0-正常1-封禁
*/
private Integer status;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}

View File

@@ -0,0 +1,74 @@
package com.xy.xyaicpzs.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
/**
* 会员码视图对象
*/
@Data
@Schema(description = "会员码视图对象")
public class VipCodeVO {
/**
* 会员码
*/
@Schema(description = "会员码")
private String code;
/**
* 会员有效月数(1/12)
*/
@Schema(description = "会员有效月数")
private Integer vipExpireTime;
/**
* 会员编号6位数如100001
*/
@Schema(description = "会员编号6位数")
private Integer vipNumber;
/**
* 是否使用0-未使用1-已使用
*/
@Schema(description = "是否使用0-未使用1-已使用")
private Integer isUse;
/**
* 创建人ID
*/
@Schema(description = "创建人ID")
private Long createdUserId;
/**
* 创建人名称
*/
@Schema(description = "创建人名称")
private String createdUserName;
/**
* 使用人ID
*/
@Schema(description = "使用人ID")
private Long usedUserId;
/**
* 使用人名称
*/
@Schema(description = "使用人名称")
private String usedUserName;
/**
* 创建时间
*/
@Schema(description = "创建时间")
private Date createTime;
/**
* 创建时间
*/
@Schema(description = "更新时间")
private Date updateTime;
}

View File

@@ -0,0 +1,33 @@
package com.xy.xyaicpzs.exception;
import com.xy.xyaicpzs.common.ErrorCode;
/**
* 自定义异常类
*/
public class BusinessException extends RuntimeException {
/**
* 错误码
*/
private final int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
}
public BusinessException(ErrorCode errorCode, String message) {
super(message);
this.code = errorCode.getCode();
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,28 @@
package com.xy.xyaicpzs.exception;
import com.xy.xyaicpzs.common.ErrorCode;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.common.response.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理器
*/
//@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ApiResponse<?> businessExceptionHandler(BusinessException e) {
log.error("businessException: " + e.getMessage(), e);
return ResultUtils.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(RuntimeException.class)
public ApiResponse<?> runtimeExceptionHandler(RuntimeException e) {
log.error("runtimeException", e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, e.getMessage());
}
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.BlueHistory100;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【blue_history_100(蓝球最近100期数据表)】的数据库操作Mapper
* @createDate 2025-06-14 10:40:04
* @Entity com.xy.xyaicpzs.domain.entity.BlueHistory100
*/
public interface BlueHistory100Mapper extends BaseMapper<BlueHistory100> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.BlueHistoryAll;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【blue_history_all(蓝球全部历史数据表)】的数据库操作Mapper
* @createDate 2025-06-14 10:40:07
* @Entity com.xy.xyaicpzs.domain.entity.BlueHistoryAll
*/
public interface BlueHistoryAllMapper extends BaseMapper<BlueHistoryAll> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.BlueHistoryTop100;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【blue_history_top_100(创建蓝球100期数据排行表)】的数据库操作Mapper
* @createDate 2025-06-14 10:40:13
* @Entity com.xy.xyaicpzs.domain.entity.BlueHistoryTop100
*/
public interface BlueHistoryTop100Mapper extends BaseMapper<BlueHistoryTop100> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.BlueHistoryTop;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【blue_history_top(蓝球历史数据排行表)】的数据库操作Mapper
* @createDate 2025-06-14 10:40:10
* @Entity com.xy.xyaicpzs.domain.entity.BlueHistoryTop
*/
public interface BlueHistoryTopMapper extends BaseMapper<BlueHistoryTop> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.ChatMessage;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【chat_message(聊天消息表)】的数据库操作Mapper
* @createDate 2025-07-07 17:37:15
* @Entity com.xy.xyaicpzs.domain.entity.ChatMessage
*/
public interface ChatMessageMapper extends BaseMapper<ChatMessage> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.History100;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【history_100(最近100期数据表)】的数据库操作Mapper
* @createDate 2025-06-14 09:48:05
* @Entity generator.domain.History100
*/
public interface History100Mapper extends BaseMapper<History100> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.HistoryAll;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【history_all(历史数据表)】的数据库操作Mapper
* @createDate 2025-06-14 09:48:10
* @Entity generator.domain.HistoryAll
*/
public interface HistoryAllMapper extends BaseMapper<HistoryAll> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.HistoryTop100;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【history_top_100(创建100期数据排行表)】的数据库操作Mapper
* @createDate 2025-06-14 09:48:16
* @Entity generator.domain.HistoryTop100
*/
public interface HistoryTop100Mapper extends BaseMapper<HistoryTop100> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.HistoryTop;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【history_top(历史数据排行表)】的数据库操作Mapper
* @createDate 2025-06-14 09:48:13
* @Entity generator.domain.HistoryTop
*/
public interface HistoryTopMapper extends BaseMapper<HistoryTop> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.LotteryDraws;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【lottery_draws(彩票开奖信息表)】的数据库操作Mapper
* @createDate 2025-06-14 16:41:29
* @Entity com.xy.xyaicpzs.domain.entity.LotteryDraws
*/
public interface LotteryDrawsMapper extends BaseMapper<LotteryDraws> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.OperationHistory;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【operation_history(操作历史记录表)】的数据库操作Mapper
* @createDate 2025-06-19 14:51:51
* @Entity com.xy.xyaicpzs.domain.entity.OperationHistory
*/
public interface OperationHistoryMapper extends BaseMapper<OperationHistory> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.PredictRecord;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【predict_record(彩票开奖信息表)】的数据库操作Mapper
* @createDate 2025-06-16 13:17:53
* @Entity com.xy.xyaicpzs.domain.entity.PredictRecord
*/
public interface PredictRecordMapper extends BaseMapper<PredictRecord> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.T11;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【t11(t11表蓝球组红球的面系数)】的数据库操作Mapper
* @createDate 2025-06-14 16:25:23
* @Entity com.xy.xyaicpzs.domain.entity.T11
*/
public interface T11Mapper extends BaseMapper<T11> {
}

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