Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 391a4c2ccb | |||
| 92363c8185 | |||
| 73b6c01cf9 | |||
| f703344485 | |||
| d9049095e3 | |||
| b2f0f91712 | |||
| ac93a5385e | |||
| 644ceed436 | |||
| 77ea55eda2 | |||
| 0f83cfc9b9 | |||
| 83749fc647 | |||
| bd5f8a9b15 | |||
| 8889be8ca2 | |||
| 6d58222143 | |||
| c3019b741c | |||
| b5b2103372 | |||
| b234c87048 | |||
| f982aa5e0c | |||
| 22823328c0 | |||
| 323e49e375 | |||
| 526e4ac198 | |||
| 7469813e37 | |||
| 8eb4e40b57 | |||
| 311cf18708 | |||
| 1bde0af01d | |||
| 765ec6fb32 | |||
| 5cd8329519 | |||
| 1ea9170d89 | |||
| 29fc69d022 | |||
| 09e7a82882 | |||
| 1fff983d37 | |||
| 836e61b8b0 | |||
| 96044b8a88 | |||
| acb865596e | |||
| 59559f4201 | |||
| 4d516adea8 | |||
| a37368ddcf | |||
| 26dcfaf82d | |||
| e55a8866b1 | |||
| 0702135db2 | |||
| c668a76a99 | |||
| 2a1cb90766 | |||
| ce536eb5bc | |||
| 0244e330fd | |||
| 035bac10cc | |||
| cb808f9c68 | |||
| 22735aeaae | |||
| dd6eeb5dfe | |||
| ac6409f3e2 | |||
| 36a6932056 | |||
| 72dab0e968 | |||
| b716434f62 | |||
| 8ed7dd77e4 | |||
| ae1c9c695e | |||
| 66995c0c78 | |||
| 120e690795 | |||
| bc2bb25578 | |||
| b37fc5cb24 | |||
| 614dd9917f | |||
| 00bc3432bd | |||
| ffec10d1cc | |||
| 3993fa9b32 | |||
| 20b92b7d1c | |||
| 05b9a94d41 | |||
| 49f4475991 | |||
| 76bfdd03dd | |||
| c18f2fdcf7 | |||
| 74c6154c59 | |||
| 3dc9a5e2a4 | |||
| ca30fd89cb | |||
| b56e18b8ce | |||
| b031157f98 | |||
| 7262023382 | |||
| dde5825d30 | |||
| da4bc164e2 | |||
| ae95a871c8 | |||
| 4a29772de0 | |||
| 194065af8d | |||
| a118b75379 | |||
| 09d59162d0 | |||
| 1323bc7aa8 | |||
| 604245add2 | |||
| 90bbc11712 | |||
| 948d118a4f | |||
| 618d6384d9 | |||
| face5ca364 | |||
| d3869f68f8 |
+5
-8
@@ -1,19 +1,16 @@
|
||||
FROM golang:latest AS backend-builder
|
||||
RUN apt update
|
||||
RUN apt install -y build-essential
|
||||
FROM golang:alpine AS backend-builder
|
||||
WORKDIR /build
|
||||
COPY . .
|
||||
ENV CGO_ENABLED=1
|
||||
|
||||
RUN go build -ldflags="-s -w" -o bin/server
|
||||
|
||||
RUN apt install -y upx-ucl
|
||||
RUN apk add --no-cache upx
|
||||
|
||||
RUN upx --best --lzma bin/server
|
||||
|
||||
FROM golang:latest
|
||||
FROM alpine:latest
|
||||
COPY --from=backend-builder /build/bin/server /app/server
|
||||
COPY static /app/static
|
||||
COPY static/*.so /app/
|
||||
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
RUN ls -a
|
||||
|
||||
+14
-1
@@ -14,7 +14,20 @@ func Run() {
|
||||
IDType: &field.TypeInfo{
|
||||
Type: field.TypeInt64,
|
||||
},
|
||||
Features: gen.AllFeatures,
|
||||
Features: []gen.Feature{
|
||||
gen.FeaturePrivacy,
|
||||
gen.FeatureIntercept,
|
||||
gen.FeatureEntQL,
|
||||
gen.FeatureNamedEdges,
|
||||
gen.FeatureBidiEdgeRefs,
|
||||
gen.FeatureSnapshot,
|
||||
gen.FeatureSchemaConfig,
|
||||
gen.FeatureLock,
|
||||
gen.FeatureModifier,
|
||||
gen.FeatureExecQuery,
|
||||
gen.FeatureUpsert,
|
||||
gen.FeatureVersionedMigration,
|
||||
},
|
||||
})
|
||||
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
+4
-4
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"likedream-goframe/internal/api"
|
||||
"likedream-goframe/internal/application/filestorage"
|
||||
"likedream-goframe/internal/db"
|
||||
db2 "likedream-goframe/internal/common/db"
|
||||
_ "likedream-goframe/internal/ent/runtime"
|
||||
"likedream-goframe/internal/framework/redis"
|
||||
"likedream-goframe/internal/framework/schedule"
|
||||
@@ -17,10 +17,10 @@ import (
|
||||
)
|
||||
|
||||
func Run() {
|
||||
db.InitDB()
|
||||
db2.InitDB()
|
||||
// 如果没有数据,进行数据初始化
|
||||
if !db.CheckHasData() {
|
||||
db.GenerateData()
|
||||
if !db2.CheckHasData() {
|
||||
db2.GenerateData()
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(gctx.GetInitCtx())
|
||||
|
||||
+19
-8
@@ -4,20 +4,23 @@ url = "https://devocr.likedream.xyz"
|
||||
[database]
|
||||
type = "mysql"
|
||||
dsn = "test_likedream_go:EpXckXmxawDEQGxJ@tcp(database.out:3306)/test_likedream_go?parseTime=true"
|
||||
|
||||
dsn2 = "host=database.out user=dev password=JPmKjmpcaG3EYAJZ dbname=dev port=5432 sslmode=disable TimeZone=Asia/Shanghai"
|
||||
# host=localhost user=gorm password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai
|
||||
[server]
|
||||
port = 8090
|
||||
url = "https://devocr.likedream.xyz"
|
||||
openapiPath= "/api.json"
|
||||
swaggerPath= "/swagger"
|
||||
emailConfirmBaseUrl = "https://dev.likedream.xyz/confirm/"
|
||||
|
||||
emailConfirmBaseUrl = "http://localhost:3200/confirm/"
|
||||
emailResetPasswordUrl = "http://localhost:3200/reset-password/"
|
||||
balanceMigrateUrl = "http://localhost:3200/balance-migrate/"
|
||||
[env]
|
||||
# 可选值: dev, prod
|
||||
mode = "dev"
|
||||
|
||||
[ruoliCaptcha]
|
||||
baseUrl = ""
|
||||
[captcha]
|
||||
baseUrl = "http://captcha-resolver.out"
|
||||
#baseUrl = "http://localhost:8890"
|
||||
[redis]
|
||||
url = "redis://:jhkdjhkjdhsIUTYURTU_pEGmFP@database.out:56379"
|
||||
|
||||
@@ -30,7 +33,7 @@ smtpUsername = "demo@likedream.xyz"
|
||||
smtpPassword = "baca122..."
|
||||
# 发件人邮箱和显示名称
|
||||
fromEmail = "demo@likedream.xyz"
|
||||
fromName = "今日校园系统"
|
||||
fromName = "若梦系统"
|
||||
|
||||
|
||||
[s3]
|
||||
@@ -40,6 +43,14 @@ baseUrl = "http://s3.out"
|
||||
bucket = "likedream"
|
||||
|
||||
[cpdaily]
|
||||
appVersion = "5.7.0"
|
||||
appVersion = "9.7.2"
|
||||
systemVersion = "iOS 17"
|
||||
calVersion = "3.0.0"
|
||||
calVersion = "firstv"
|
||||
sliderIds = ["1018764144934965", "sdhyxy", "hqdx"]
|
||||
|
||||
|
||||
|
||||
[basePoint]
|
||||
cpdaily = "0.5"
|
||||
[task]
|
||||
concurrency = 20
|
||||
@@ -0,0 +1,403 @@
|
||||
# 刷课接口文档
|
||||
|
||||
## 接口列表
|
||||
|
||||
### 1. 获取刷课列表
|
||||
|
||||
**接口地址**: `GET /course`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| type | string | 否 | 类型,可选值:`xxt`(学习通) |
|
||||
| username | string | 否 | 账号(模糊搜索) |
|
||||
| status | string | 否 | 状态,可选值:`not_start`(未开始)、`waiting`(等待中)、`doing`(进行中)、`done`(已完成)、`fail`(失败)、`pause`(暂停) |
|
||||
| page | int | 否 | 页码,默认 1 |
|
||||
| size | int | 否 | 每页数量,默认 10 |
|
||||
|
||||
**返回示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"user_id": 1,
|
||||
"username": "学习通账号",
|
||||
"password": "",
|
||||
"type": "xxt",
|
||||
"course_name": "大学英语",
|
||||
"course_id": "123456",
|
||||
"need": "task",
|
||||
"status": "waiting",
|
||||
"task_count": 10,
|
||||
"success_count": 5,
|
||||
"exam_state": "not_start",
|
||||
"violence": "no",
|
||||
"desc": "备注信息",
|
||||
"create_time": "2024-01-01T00:00:00Z",
|
||||
"update_time": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
],
|
||||
"total": 100
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明**:
|
||||
|
||||
- `type`: 类型 - `xxt`(学习通)
|
||||
- `need`: 需要刷的内容 - `task`(任务点)、`exam`(考试)、`all`(全部)
|
||||
- `status`: 状态 - `not_start`(未开始)、`waiting`(等待中)、`doing`(进行中)、`done`(已完成)、`fail`(失败)、`pause`(暂停)
|
||||
- `exam_state`: 考试状态 - `not_start`(未开始)、`waiting`(等待中)、`doing`(进行中)、`done`(已完成)、`fail`(失败)
|
||||
- `violence`: 是否暴力刷课 - `no`(否)、`yes`(是)
|
||||
|
||||
---
|
||||
|
||||
### 2. 查询课程列表
|
||||
|
||||
**接口地址**: `POST /course/query`
|
||||
|
||||
**说明**: 通过学习通账号密码查询当前可选的课程列表,用于在添加刷课前选择课程。
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| username | string | 是 | 学习通账号 |
|
||||
| password | string | 是 | 学习通密码 |
|
||||
|
||||
**请求示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "学习通账号",
|
||||
"password": "学习通密码"
|
||||
}
|
||||
```
|
||||
|
||||
**返回示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"cpi": 123456,
|
||||
"key": "234567",
|
||||
"courseId": "345678",
|
||||
"chatId": "456789",
|
||||
"courseTeacher": "张老师",
|
||||
"courseName": "大学英语",
|
||||
"courseImage": "https://example.com/image.jpg",
|
||||
"courseImageBase64": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAA...",
|
||||
"courseDataId": 567890,
|
||||
"ContentID": 678901,
|
||||
"isstart": true,
|
||||
"state": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明**:
|
||||
|
||||
- `cpi`: 用户唯一标识
|
||||
- `key`: 课程班级ID(classId)
|
||||
- `courseId`: 课程ID(**添加刷课时需要使用**)
|
||||
- `chatId`: 聊天ID
|
||||
- `courseTeacher`: 授课教师
|
||||
- `courseName`: 课程名称(**添加刷课时需要使用**)
|
||||
- `courseImage`: 课程封面图片URL
|
||||
- `courseImageBase64`: 课程封面图片Base64编码(包含Data URL前缀,可直接用于img标签的src属性)
|
||||
- `courseDataId`: 课程数据ID
|
||||
- `ContentID`: 内容ID
|
||||
- `isstart`: 是否已开课(true:已开课,false:未开课)
|
||||
- `state`: 课程状态(0:正常,1:已结束)
|
||||
|
||||
**注意事项**:
|
||||
|
||||
- `courseImageBase64` 字段已经包含了 Data URL 前缀(如 `data:image/jpeg;base64,`),可以直接设置到 `<img>` 标签的 `src` 属性
|
||||
- 如果图片下载失败,`courseImageBase64` 字段将为空字符串,此时可以使用 `courseImage` URL 作为备用
|
||||
|
||||
**错误示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "登录失败,请检查账号密码",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 添加刷课
|
||||
|
||||
**接口地址**: `POST /course`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| username | string | 是 | 学习通账号 |
|
||||
| password | string | 是 | 学习通密码 |
|
||||
| type | string | 是 | 类型,目前只支持 `xxt`(学习通) |
|
||||
| course_name | string | 是 | 课程名称 |
|
||||
| course_id | string | 是 | 课程ID |
|
||||
| need | string | 是 | 需要刷的内容,可选值:`task`(任务点)、`exam`(考试)、`all`(全部) |
|
||||
| violence | string | 否 | 是否暴力刷课,可选值:`no`(否,默认)、`yes`(是) |
|
||||
| desc | string | 否 | 备注信息 |
|
||||
|
||||
**请求示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "学习通账号",
|
||||
"password": "学习通密码",
|
||||
"type": "xxt",
|
||||
"course_name": "大学英语",
|
||||
"course_id": "123456",
|
||||
"need": "task",
|
||||
"violence": "no",
|
||||
"desc": "备注信息"
|
||||
}
|
||||
```
|
||||
|
||||
**返回示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "添加成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
**错误示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "该课程已存在刷课任务",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 修改刷课密码
|
||||
|
||||
**接口地址**: `PUT /course/password/{id}`
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| id | int64 | 是 | 刷课任务ID |
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| password | string | 是 | 新密码 |
|
||||
|
||||
**请求示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"password": "新密码"
|
||||
}
|
||||
```
|
||||
|
||||
**返回示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "密码修改成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
**错误示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "课程不存在或无权限操作",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. 重刷课程
|
||||
|
||||
**接口地址**: `POST /course/restart/{id}`
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| id | int64 | 是 | 刷课任务ID |
|
||||
|
||||
**说明**: 只有状态为 `done`(已完成)的课程才能重刷,重刷后会重置课程状态、任务数量、成功数量和考试状态。
|
||||
|
||||
**返回示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "重刷成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
**错误示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "只有已完成的课程才能重刷",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. 暂停/恢复刷课
|
||||
|
||||
**接口地址**: `POST /course/pause/{id}`
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| id | int64 | 是 | 刷课任务ID |
|
||||
|
||||
**说明**: 该接口会在暂停和恢复之间切换:
|
||||
- 如果当前状态是 `pause`(暂停),则恢复为 `waiting`(等待中)
|
||||
- 如果当前状态不是暂停,则设置为 `pause`(暂停)
|
||||
|
||||
**返回示例(暂停)**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "已暂停刷课",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
**返回示例(恢复)**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "已恢复刷课",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. 删除刷课
|
||||
|
||||
**接口地址**: `DELETE /course/{id}`
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| id | int64 | 是 | 刷课任务ID |
|
||||
|
||||
**返回示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "删除成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
**错误示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "课程不存在或无权限操作",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 枚举值说明
|
||||
|
||||
### 类型 (type)
|
||||
|
||||
| 值 | 说明 |
|
||||
|-----|------|
|
||||
| xxt | 学习通 |
|
||||
|
||||
### 需要刷的内容 (need)
|
||||
|
||||
| 值 | 说明 |
|
||||
|-----|------|
|
||||
| task | 任务点 |
|
||||
| exam | 考试 |
|
||||
| all | 全部 |
|
||||
|
||||
### 状态 (status)
|
||||
|
||||
| 值 | 说明 |
|
||||
|-----|------|
|
||||
| not_start | 未开始 |
|
||||
| waiting | 等待中 |
|
||||
| doing | 进行中 |
|
||||
| done | 已完成 |
|
||||
| fail | 失败 |
|
||||
| pause | 暂停 |
|
||||
|
||||
### 考试状态 (exam_state)
|
||||
|
||||
| 值 | 说明 |
|
||||
|-----|------|
|
||||
| not_start | 未开始 |
|
||||
| waiting | 等待中 |
|
||||
| doing | 进行中 |
|
||||
| done | 已完成 |
|
||||
| fail | 失败 |
|
||||
|
||||
### 是否暴力刷课 (violence)
|
||||
|
||||
| 值 | 说明 |
|
||||
|-----|------|
|
||||
| no | 否 |
|
||||
| yes | 是 |
|
||||
|
||||
---
|
||||
|
||||
## 通用说明
|
||||
|
||||
### 认证
|
||||
|
||||
所有接口都需要用户认证,需要在请求头中携带认证信息(具体认证方式根据项目配置)。
|
||||
|
||||
### 权限
|
||||
|
||||
用户只能操作自己创建的刷课任务,无法访问或修改其他用户的数据。
|
||||
|
||||
### 错误码
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 200 | 成功 |
|
||||
| 400 | 业务错误(如参数错误、权限不足等) |
|
||||
| 401 | 未认证 |
|
||||
| 500 | 服务器错误 |
|
||||
@@ -1,6 +1,6 @@
|
||||
module likedream-goframe
|
||||
|
||||
go 1.24
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
entgo.io/ent v0.14.5
|
||||
@@ -28,10 +28,11 @@ require (
|
||||
github.com/wenlng/go-captcha-assets v1.0.7
|
||||
github.com/wenlng/go-captcha/v2 v2.0.4
|
||||
github.com/wneessen/go-mail v0.6.2
|
||||
github.com/yalue/onnxruntime_go v1.9.0
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.39.0
|
||||
golang.org/x/crypto v0.43.0
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/gorm v1.31.1
|
||||
resty.dev/v3 v3.0.0-beta.3
|
||||
)
|
||||
|
||||
@@ -69,6 +70,12 @@ require (
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.18.1 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.9 // indirect
|
||||
@@ -98,13 +105,13 @@ require (
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/image v0.29.0 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/mod v0.28.0 // indirect
|
||||
golang.org/x/net v0.45.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.34.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -111,6 +111,18 @@ github.com/hashicorp/hcl/v2 v2.18.1 h1:6nxnOJFku1EuSawSD81fuviYUV8DxFr3fp2dUi3ZY
|
||||
github.com/hashicorp/hcl/v2 v2.18.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
|
||||
github.com/hibiken/asynq v0.25.1 h1:phj028N0nm15n8O2ims+IvJ2gz4k2auvermngh9JhTw=
|
||||
github.com/hibiken/asynq v0.25.1/go.mod h1:pazWNOLBu0FEynQRBvHA26qdIKRSmfdIfUm4HdsLmXg=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/joomcode/errorx v1.2.0 h1:7Y/fguon+9r6a/75Rv3nrUwS7nXNEcJjLShjCvz00Og=
|
||||
github.com/joomcode/errorx v1.2.0/go.mod h1:Mbz68VA9hsQLT50iCQQUZ2Z1XYAKYB4EoFkFCTFyiJM=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
@@ -134,8 +146,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
|
||||
@@ -177,6 +189,7 @@ github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqe
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -202,8 +215,6 @@ github.com/wenlng/go-captcha/v2 v2.0.4 h1:5cSUF36ZyA03qeDMjKmeXGpbYJMXEexZIYK3Vg
|
||||
github.com/wenlng/go-captcha/v2 v2.0.4/go.mod h1:5hac1em3uXoyC5ipZ0xFv9umNM/waQvYAQdr0cx/h34=
|
||||
github.com/wneessen/go-mail v0.6.2 h1:c6V7c8D2mz868z9WJ+8zDKtUyLfZ1++uAZmo2GRFji8=
|
||||
github.com/wneessen/go-mail v0.6.2/go.mod h1:L/PYjPK3/2ZlNb2/FjEBIn9n1rUWjW+Toy531oVmeb4=
|
||||
github.com/yalue/onnxruntime_go v1.9.0 h1:AhgkpBjphJZsHT5karKt93xPkPFNP0Iz6ENUbNAFQU4=
|
||||
github.com/yalue/onnxruntime_go v1.9.0/go.mod h1:b4X26A8pekNb1ACJ58wAXgNKeUCGEAQ9dmACut9Sm/4=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
@@ -236,8 +247,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
|
||||
golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas=
|
||||
golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA=
|
||||
@@ -246,8 +257,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
@@ -257,8 +268,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
||||
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -267,8 +278,8 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -286,8 +297,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -308,8 +319,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -318,8 +329,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
@@ -331,6 +342,10 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
resty.dev/v3 v3.0.0-beta.3 h1:3kEwzEgCnnS6Ob4Emlk94t+I/gClyoah7SnNi67lt+E=
|
||||
resty.dev/v3 v3.0.0-beta.3/go.mod h1:OgkqiPvTDtOuV4MGZuUDhwOpkY8enjOsjjMzeOHefy4=
|
||||
|
||||
@@ -2,15 +2,17 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"likedream-goframe/internal/common/cst"
|
||||
"likedream-goframe/internal/controller/agent"
|
||||
"likedream-goframe/internal/controller/card"
|
||||
"likedream-goframe/internal/controller/common"
|
||||
"likedream-goframe/internal/controller/course"
|
||||
"likedream-goframe/internal/controller/cpdaily"
|
||||
"likedream-goframe/internal/controller/menu"
|
||||
"likedream-goframe/internal/controller/music"
|
||||
"likedream-goframe/internal/controller/role"
|
||||
"likedream-goframe/internal/controller/user"
|
||||
"likedream-goframe/internal/controller/user_login"
|
||||
"likedream-goframe/internal/cst"
|
||||
"likedream-goframe/internal/middleware"
|
||||
"likedream-goframe/internal/utils"
|
||||
|
||||
@@ -53,6 +55,12 @@ func StartServer(ctx context.Context) {
|
||||
cpdailyGroup.Bind(cpdaily.NewCpTaskCtrlV1())
|
||||
cpdailyGroup.Bind(cpdaily.NewCpImageCtrlV1())
|
||||
cpdailyGroup.Bind(cpdaily.NewCpTaskLogCtrlV1())
|
||||
// 娱乐
|
||||
musicGroup := group.Group("/music").Middleware(middleware.AuthHandler, middleware.PermissionHandler)
|
||||
musicGroup.Bind(music.NewMusicCtrlV1())
|
||||
// 网课
|
||||
courseGroup := group.Group("/course").Middleware(middleware.AuthHandler, middleware.PermissionHandler)
|
||||
courseGroup.Bind(course.NewXxtCtrlV1())
|
||||
})
|
||||
|
||||
s.SetPort(port.Int())
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"likedream-goframe/internal/ent/courseentity"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type CourseListReq struct {
|
||||
g.Meta `path:"/course" method:"get" tags:"Course" summary:"获取刷课列表"`
|
||||
Type courseentity.Type `json:"type"`
|
||||
Username string `json:"username"`
|
||||
Status courseentity.Status `json:"status"`
|
||||
Page int `json:"page" d:"1"`
|
||||
Size int `json:"size" d:"10"`
|
||||
}
|
||||
|
||||
type AddCourseReq struct {
|
||||
g.Meta `path:"/course" method:"post" tags:"Course" summary:"添加刷课"`
|
||||
Username string `json:"username" v:"required#账号不能为空"`
|
||||
Password string `json:"password" v:"required#密码不能为空"`
|
||||
Type courseentity.Type `json:"type" v:"required#类型不能为空"`
|
||||
CourseName string `json:"course_name" v:"required#课程名称不能为空"`
|
||||
CourseID string `json:"course_id" v:"required#课程ID不能为空"`
|
||||
Need courseentity.Need `json:"need" v:"required#需要刷的内容不能为空"`
|
||||
Violence courseentity.Violence `json:"violence" d:"no"`
|
||||
Desc string `json:"desc"`
|
||||
}
|
||||
|
||||
type UpdateCoursePasswordReq struct {
|
||||
g.Meta `path:"/course/password/{id}" method:"put" tags:"Course" summary:"修改刷课密码"`
|
||||
ID int64 `json:"id"`
|
||||
Password string `json:"password" v:"required#密码不能为空"`
|
||||
}
|
||||
|
||||
type RestartCourseReq struct {
|
||||
g.Meta `path:"/course/restart/{id}" method:"post" tags:"Course" summary:"重刷课程"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
type PauseCourseReq struct {
|
||||
g.Meta `path:"/course/pause/{id}" method:"post" tags:"Course" summary:"暂停刷课"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
type DeleteCourseReq struct {
|
||||
g.Meta `path:"/course/{id}" method:"delete" tags:"Course" summary:"删除刷课"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
type QueryCourseReq struct {
|
||||
g.Meta `path:"/course/query" method:"post" tags:"Course" summary:"查询课程列表"`
|
||||
Username string `json:"username" v:"required#账号不能为空"`
|
||||
Password string `json:"password" v:"required#密码不能为空"`
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package v1
|
||||
|
||||
// TaskStatisticsItem 每日任务统计项
|
||||
type TaskStatisticsItem struct {
|
||||
Date string `json:"date"` // 日期 格式:2006-01-02
|
||||
PendingTask int `json:"pendingTask"` // 需要完成的任务数
|
||||
SuccessTask int `json:"successTask"` // 成功执行的任务数
|
||||
}
|
||||
@@ -67,3 +67,12 @@ type PauseTaskReq struct {
|
||||
g.Meta `path:"/task/pause/{id}" method:"post" tags:"CpTask" summary:"暂停任务"`
|
||||
Id int64 `json:"id"`
|
||||
}
|
||||
|
||||
type FindLocationReq struct {
|
||||
g.Meta `path:"/task/location" method:"get" tags:"CpTask" summary:"查找地址"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type GetTaskStatisticsReq struct {
|
||||
g.Meta `path:"/task/statistics" method:"get" tags:"CpTask" summary:"获取近七日任务统计数据"`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package v1
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
import (
|
||||
"likedream-goframe/internal/framework/captcha"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type CpSchoolListReq struct {
|
||||
g.Meta `path:"/school" method:"get" tags:"CpSchool" summary:"获取cp学校列表"`
|
||||
@@ -14,6 +18,21 @@ type CpUserAddReq struct {
|
||||
SchoolId string `json:"school_id"`
|
||||
Email string `json:"email" v:"email|regex:^.*@qq\\.com$#邮箱格式不正确|必须是QQ邮箱"`
|
||||
}
|
||||
type LoginGetCode struct {
|
||||
g.Meta `path:"/user/getCode" method:"post" tags:"CpUser" summary:"获取手机号登录验证码"`
|
||||
Mobile string `json:"mobile" v:"required#手机号不能为空"`
|
||||
CaptchaKey string `json:"captchaKey" v:"required#验证码不能不能为空"`
|
||||
Dots []captcha.ConfirmData `json:"dots" v:"required#验证码不能为空"`
|
||||
}
|
||||
|
||||
type AddByCode struct {
|
||||
g.Meta `path:"/user/addByCode" method:"post" tags:"CpUser" summary:"手机号登录"`
|
||||
CaptchaKey string `json:"captchaKey" v:"required#验证码不能不能为空"`
|
||||
Dots []captcha.ConfirmData `json:"dots" v:"required#验证码不能为空"`
|
||||
Mobile string `json:"mobile" v:"required#手机号不能为空"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
type CpUserConfirmAddReq struct {
|
||||
g.Meta `path:"/user/confirm" method:"post" tags:"CpUser" summary:"多因子确认添加cp用户"`
|
||||
Key string `json:"key"`
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package v1
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
type SearchMusicReq struct {
|
||||
g.Meta `path:"/search" method:"get" tags:"Music" summary:"搜索音乐"`
|
||||
Keyword string `json:"keyword"`
|
||||
}
|
||||
|
||||
type DownloadMusicReq struct {
|
||||
g.Meta `path:"/download" method:"get" tags:"Music" summary:"下载音乐"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
@@ -12,22 +12,64 @@ type CaptchaReq struct {
|
||||
|
||||
type UserLoginReq struct {
|
||||
g.Meta `path:"/user/login" method:"POST" tags:"UserLogin" summary:"用户登录"`
|
||||
Username string `json:"username" v:"required"`
|
||||
Password string `json:"password" v:"required"`
|
||||
CaptchaKey string `json:"captchaKey" v:"required"`
|
||||
Dots []captcha.ConfirmData `json:"dots" v:"required"`
|
||||
Username string `json:"username" v:"required#用户名或密码不能为空"`
|
||||
Password string `json:"password" v:"required#用户名或密码不能为空"`
|
||||
CaptchaKey string `json:"captchaKey" v:"required#验证码不能不能为空"`
|
||||
Dots []captcha.ConfirmData `json:"dots" v:"required#验证码不能为空"`
|
||||
}
|
||||
|
||||
type UserRegisterReq struct {
|
||||
g.Meta `path:"/user/register" method:"POST" tags:"UserLogin" summary:"用户注册"`
|
||||
Email string `json:"email" v:"required|email|regex:^.*@qq\\.com$#邮箱不能为空|邮箱格式不正确|必须是QQ邮箱"`
|
||||
Username string `json:"username" v:"required|length:2,20#用户名不能为空|用户名长度应在2-20之间" `
|
||||
Password string `json:"password" v:"required|length:6,20#密码不能为空|密码长度应在6-20之间" `
|
||||
ConfirmPassword string `json:"confirmPassword" v:"required|same:password#确认密码不能为空|两次输入的密码不一致"`
|
||||
InviteCode string `json:"inviteCode"`
|
||||
Email string `json:"email" v:"required|email|regex:^.*@qq\\.com$#邮箱不能为空|邮箱格式不正确|必须是QQ邮箱"`
|
||||
Username string `json:"username" v:"required|length:2,20#用户名不能为空|用户名长度应在2-20之间" `
|
||||
Password string `json:"password" v:"required|length:6,20#密码不能为空|密码长度应在6-20之间" `
|
||||
ConfirmPassword string `json:"confirmPassword" v:"required|same:password#确认密码不能为空|两次输入的密码不一致"`
|
||||
InviteCode string `json:"inviteCode"`
|
||||
CaptchaKey string `json:"captchaKey" v:"required#验证码不能不能为空"`
|
||||
Dots []captcha.ConfirmData `json:"dots" v:"required#验证码不能为空"`
|
||||
}
|
||||
|
||||
type UserConfirmReq struct {
|
||||
g.Meta `path:"/user/confirm" method:"POST" tags:"UserLogin" summary:"邮箱二次验证"`
|
||||
Token string `json:"token" v:"required"`
|
||||
}
|
||||
|
||||
type ResendVerificationReq struct {
|
||||
g.Meta `path:"/user/resendVerification" method:"POST" tags:"UserLogin" summary:"重新发送验证邮件"`
|
||||
Email string `json:"email" v:"required|email|regex:^.*@qq\\.com$#邮箱不能为空|邮箱格式不正确|必须是QQ邮箱"`
|
||||
CaptchaKey string `json:"captchaKey" v:"required#验证码不能不能为空"`
|
||||
Dots []captcha.ConfirmData `json:"dots" v:"required#验证码不能为空"`
|
||||
}
|
||||
|
||||
type ForgotPasswordReq struct {
|
||||
g.Meta `path:"/user/forgotPassword" method:"POST" tags:"UserLogin" summary:"忘记密码-发送重置密码邮件"`
|
||||
Email string `json:"email" v:"required|email|regex:^.*@qq\\.com$#邮箱不能为空|邮箱格式不正确|必须是QQ邮箱"`
|
||||
CaptchaKey string `json:"captchaKey" v:"required#验证码不能不能为空"`
|
||||
Dots []captcha.ConfirmData `json:"dots" v:"required#验证码不能为空"`
|
||||
}
|
||||
|
||||
type ForgetResetPasswordReq struct {
|
||||
g.Meta `path:"/user/forgetResetPassword" method:"POST" tags:"UserLogin" summary:"忘记密码-重置密码"`
|
||||
Token string `json:"token" v:"required#token不能为空"`
|
||||
NewPassword string `json:"newPassword" v:"required|length:6,20#密码不能为空|密码长度应在6-20之间"`
|
||||
ConfirmPassword string `json:"confirmPassword" v:"required|same:newPassword#确认密码不能为空|两次输入的密码不一致"`
|
||||
}
|
||||
|
||||
type ResetPasswordInfoReq struct {
|
||||
g.Meta `path:"/user/resetPassword/info" method:"POST" tags:"UserLogin" summary:"获取重置密码用户信息"`
|
||||
Token string `json:"token" v:"required#token不能为空"`
|
||||
}
|
||||
|
||||
type RequestBalanceMigrateReq struct {
|
||||
g.Meta `path:"/user/requestBalanceMigrate" method:"POST" tags:"UserLogin" summary:"请求积分转移链接"`
|
||||
Email string `json:"email" v:"required|email|regex:^.*@qq\\.com$#邮箱不能为空|邮箱格式不正确|必须是QQ邮箱"`
|
||||
CaptchaKey string `json:"captchaKey" v:"required#验证码不能不能为空"`
|
||||
Dots []captcha.ConfirmData `json:"dots" v:"required#验证码不能为空"`
|
||||
}
|
||||
|
||||
type BalanceMigrateReq struct {
|
||||
g.Meta `path:"/user/balanceMigrate" method:"POST" tags:"UserLogin" summary:"执行积分转移" auth:"false"`
|
||||
Token string `json:"token" v:"required#token不能为空"`
|
||||
CaptchaKey string `json:"captchaKey" v:"required#验证码不能不能为空"`
|
||||
Dots []captcha.ConfirmData `json:"dots" v:"required#验证码不能为空"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
"likedream-goframe/internal/common/cst"
|
||||
"likedream-goframe/internal/utils"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/samber/oops"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func ApiSemiOCRVerification(bytes []byte, shape1, shape2 int64) string {
|
||||
data := Bytes2Base64(bytes)
|
||||
ctx := gctx.GetInitCtx()
|
||||
baseUrl := g.Config().MustGetWithEnv(ctx, cst.ConfigCaptchaBaseUrl).String() + "/semiOcr"
|
||||
client := httpclient.Cpdaily.Clone(ctx)
|
||||
client.SetDoNotParseResponse(false)
|
||||
post, err := client.R().SetBody(map[string]any{
|
||||
"base64": data,
|
||||
"shape1": shape1,
|
||||
"shape2": shape2,
|
||||
}).Post(baseUrl)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
With("SemiOCRVerification", baseUrl).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
result := post.String()
|
||||
return gjson.Get(result, "data").String()
|
||||
}
|
||||
|
||||
func ApiAutoOCRVerification(bytes []byte) string {
|
||||
data := Bytes2Base64(bytes)
|
||||
ctx := gctx.GetInitCtx()
|
||||
baseUrl := g.Config().MustGetWithEnv(ctx, cst.ConfigCaptchaBaseUrl).String() + "/ocr"
|
||||
client := httpclient.Cpdaily.Clone(ctx)
|
||||
client.SetDoNotParseResponse(false)
|
||||
post, err := client.R().SetBody(map[string]any{
|
||||
"base64": data,
|
||||
}).Post(baseUrl)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
With("AutoOCRVerification", baseUrl).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
result := post.String()
|
||||
return gjson.Get(result, "data").String()
|
||||
}
|
||||
func Bytes2Base64(data []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(data)
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
package classifier
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"likedream-goframe/internal/framework/logger"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
ort "github.com/yalue/onnxruntime_go"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ImageClassifier struct {
|
||||
session *ort.AdvancedSession
|
||||
inputShape []int64
|
||||
classes []string
|
||||
inputTensor *ort.Tensor[float32]
|
||||
outputTensor *ort.Tensor[float32]
|
||||
}
|
||||
|
||||
// NewImageClassifier 创建新的图片分类器
|
||||
func NewImageClassifier(modelPath, classesPath string) (*ImageClassifier, error) {
|
||||
// 初始化ONNX Runtime
|
||||
exePath := strings.ToLower(os.Args[0])
|
||||
// 获取绝对路径
|
||||
absPath, err := filepath.Abs(exePath)
|
||||
dir := filepath.Dir(absPath)
|
||||
systemOS := runtime.GOOS
|
||||
switch systemOS {
|
||||
case "windows":
|
||||
dir = filepath.Join(dir, "onnxruntime.dll")
|
||||
break
|
||||
case "darwin":
|
||||
dir = filepath.Join(dir, "onnxruntime.dylib")
|
||||
break
|
||||
case "linux":
|
||||
dir = filepath.Join(dir, "onnxruntime.so")
|
||||
break
|
||||
}
|
||||
ort.SetSharedLibraryPath(dir)
|
||||
err = ort.InitializeEnvironment()
|
||||
if err != nil {
|
||||
logger.Logger.Info("初始化onnx:", zap.String("绝对路径", absPath), zap.String("父文件夹", dir))
|
||||
return nil, fmt.Errorf("初始化ONNX Runtime失败: %v", err)
|
||||
}
|
||||
|
||||
// 加载类别名称
|
||||
classes, err := loadClasses(classesPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("加载类别失败: %v", err)
|
||||
}
|
||||
|
||||
// 创建session选项
|
||||
options, err := ort.NewSessionOptions()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建session选项失败: %v", err)
|
||||
}
|
||||
defer options.Destroy()
|
||||
|
||||
// 创建输入输出tensor
|
||||
inputShape := ort.NewShape(1, 3, 224, 224)
|
||||
outputShape := ort.NewShape(1, int64(len(classes)))
|
||||
|
||||
inputTensor, err := ort.NewEmptyTensor[float32](inputShape)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建输入tensor失败: %v", err)
|
||||
}
|
||||
|
||||
outputTensor, err := ort.NewEmptyTensor[float32](outputShape)
|
||||
if err != nil {
|
||||
inputTensor.Destroy()
|
||||
return nil, fmt.Errorf("创建输出tensor失败: %v", err)
|
||||
}
|
||||
|
||||
// 加载模型
|
||||
session, err := ort.NewAdvancedSession(modelPath,
|
||||
[]string{"input"}, []string{"output"},
|
||||
[]ort.ArbitraryTensor{inputTensor}, []ort.ArbitraryTensor{outputTensor},
|
||||
options)
|
||||
if err != nil {
|
||||
inputTensor.Destroy()
|
||||
outputTensor.Destroy()
|
||||
return nil, fmt.Errorf("加载模型失败: %v", err)
|
||||
}
|
||||
|
||||
return &ImageClassifier{
|
||||
session: session,
|
||||
inputShape: []int64{1, 3, 224, 224},
|
||||
classes: classes,
|
||||
inputTensor: inputTensor,
|
||||
outputTensor: outputTensor,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// loadClasses 从文件加载类别名称
|
||||
func loadClasses(path string) ([]string, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var classes []string
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
classes = append(classes, scanner.Text())
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return classes, nil
|
||||
}
|
||||
|
||||
// Predict 预测图片类别
|
||||
func (ic *ImageClassifier) Predict(imgPath string) (string, float32, error) {
|
||||
// 加载并预处理图片
|
||||
inputData, err := ic.preprocessImage(imgPath)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("预处理图片失败: %v", err)
|
||||
}
|
||||
|
||||
// 设置输入数据
|
||||
copy(ic.inputTensor.GetData(), inputData)
|
||||
|
||||
// 运行推理
|
||||
err = ic.session.Run()
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("推理失败: %v", err)
|
||||
}
|
||||
|
||||
// 获取输出数据
|
||||
outputData := ic.outputTensor.GetData()
|
||||
|
||||
// 找到最大概率的类别
|
||||
maxIdx := 0
|
||||
maxProb := outputData[0]
|
||||
for i, prob := range outputData {
|
||||
if prob > maxProb {
|
||||
maxProb = prob
|
||||
maxIdx = i
|
||||
}
|
||||
}
|
||||
|
||||
// 应用softmax获取概率
|
||||
expSum := float32(0)
|
||||
for _, v := range outputData {
|
||||
expSum += exp(v)
|
||||
}
|
||||
probability := exp(outputData[maxIdx]) / expSum
|
||||
|
||||
return ic.classes[maxIdx], probability, nil
|
||||
}
|
||||
|
||||
// PredictFromBytes 从字节数据预测图片类别
|
||||
func (ic *ImageClassifier) PredictFromBytes(imgData []byte) (string, float32, error) {
|
||||
// 预处理图片
|
||||
inputData, err := ic.preprocessImageBytes(imgData)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("预处理图片失败: %v", err)
|
||||
}
|
||||
|
||||
// 设置输入数据
|
||||
copy(ic.inputTensor.GetData(), inputData)
|
||||
|
||||
// 运行推理
|
||||
err = ic.session.Run()
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("推理失败: %v", err)
|
||||
}
|
||||
|
||||
// 获取输出数据
|
||||
outputData := ic.outputTensor.GetData()
|
||||
|
||||
// 找到最大概率的类别
|
||||
maxIdx := 0
|
||||
maxProb := outputData[0]
|
||||
for i, prob := range outputData {
|
||||
if prob > maxProb {
|
||||
maxProb = prob
|
||||
maxIdx = i
|
||||
}
|
||||
}
|
||||
|
||||
// 应用softmax获取概率
|
||||
expSum := float32(0)
|
||||
for _, v := range outputData {
|
||||
expSum += exp(v)
|
||||
}
|
||||
probability := exp(outputData[maxIdx]) / expSum
|
||||
|
||||
return ic.classes[maxIdx], probability, nil
|
||||
}
|
||||
|
||||
// Close 关闭分类器
|
||||
func (ic *ImageClassifier) Close() error {
|
||||
if ic.inputTensor != nil {
|
||||
ic.inputTensor.Destroy()
|
||||
}
|
||||
if ic.outputTensor != nil {
|
||||
ic.outputTensor.Destroy()
|
||||
}
|
||||
if ic.session != nil {
|
||||
ic.session.Destroy()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// exp 计算e的x次方(简单实现)
|
||||
func exp(x float32) float32 {
|
||||
// 使用泰勒级数近似
|
||||
result := float32(1.0)
|
||||
term := float32(1.0)
|
||||
for i := 1; i < 20; i++ {
|
||||
term *= x / float32(i)
|
||||
result += term
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package classifier
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"os"
|
||||
)
|
||||
|
||||
// preprocessImage 预处理图片文件
|
||||
func (ic *ImageClassifier) preprocessImage(imgPath string) ([]float32, error) {
|
||||
file, err := os.Open(imgPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
img, _, err := image.Decode(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ic.processImage(img)
|
||||
}
|
||||
|
||||
// preprocessImageBytes 预处理图片字节数据
|
||||
func (ic *ImageClassifier) preprocessImageBytes(imgData []byte) ([]float32, error) {
|
||||
img, _, err := image.Decode(bytes.NewReader(imgData))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ic.processImage(img)
|
||||
}
|
||||
|
||||
// processImage 处理图片为模型输入格式
|
||||
func (ic *ImageClassifier) processImage(img image.Image) ([]float32, error) {
|
||||
// 调整图片大小到224x224
|
||||
resized := resizeImage(img, 224, 224)
|
||||
|
||||
// 转换为float32数组,并进行归一化
|
||||
// ImageNet的均值和标准差
|
||||
mean := []float32{0.485, 0.456, 0.406}
|
||||
std := []float32{0.229, 0.224, 0.225}
|
||||
|
||||
data := make([]float32, 3*224*224)
|
||||
|
||||
for y := 0; y < 224; y++ {
|
||||
for x := 0; x < 224; x++ {
|
||||
r, g, b, _ := resized.At(x, y).RGBA()
|
||||
|
||||
// 转换为0-1范围
|
||||
rNorm := float32(r) / 65535.0
|
||||
gNorm := float32(g) / 65535.0
|
||||
bNorm := float32(b) / 65535.0
|
||||
|
||||
// 应用归一化
|
||||
idx := y*224 + x
|
||||
data[idx] = (rNorm - mean[0]) / std[0] // R通道
|
||||
data[224*224+idx] = (gNorm - mean[1]) / std[1] // G通道
|
||||
data[2*224*224+idx] = (bNorm - mean[2]) / std[2] // B通道
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// resizeImage 调整图片大小
|
||||
func resizeImage(img image.Image, width, height int) image.Image {
|
||||
bounds := img.Bounds()
|
||||
srcW := bounds.Dx()
|
||||
srcH := bounds.Dy()
|
||||
|
||||
dst := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
|
||||
for y := 0; y < height; y++ {
|
||||
for x := 0; x < width; x++ {
|
||||
// 简单的最近邻插值
|
||||
srcX := x * srcW / width
|
||||
srcY := y * srcH / height
|
||||
|
||||
if srcX >= srcW {
|
||||
srcX = srcW - 1
|
||||
}
|
||||
if srcY >= srcH {
|
||||
srcY = srcH - 1
|
||||
}
|
||||
|
||||
dst.Set(x, y, img.At(srcX, srcY))
|
||||
}
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// centerCrop 中心裁剪图片
|
||||
func centerCrop(img image.Image, size int) image.Image {
|
||||
bounds := img.Bounds()
|
||||
width := bounds.Dx()
|
||||
height := bounds.Dy()
|
||||
|
||||
var x0, y0 int
|
||||
if width > height {
|
||||
x0 = (width - height) / 2
|
||||
y0 = 0
|
||||
width = height
|
||||
} else {
|
||||
x0 = 0
|
||||
y0 = (height - width) / 2
|
||||
height = width
|
||||
}
|
||||
|
||||
cropped := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
for y := 0; y < height; y++ {
|
||||
for x := 0; x < width; x++ {
|
||||
cropped.Set(x, y, img.At(x0+x, y0+y))
|
||||
}
|
||||
}
|
||||
|
||||
return resizeImage(cropped, size, size)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package classifier
|
||||
|
||||
import "likedream-goframe/internal/utils"
|
||||
|
||||
var imageClassifier, _ = NewImageClassifier("static/model.onnx", "static/classes.txt")
|
||||
|
||||
// 图片数据 & 类别名称
|
||||
// return 是否匹配
|
||||
func ClassifyImage(imgData []byte, className string) bool {
|
||||
if imageClassifier == nil {
|
||||
c, err := NewImageClassifier("static/model.onnx", "static/classes.txt")
|
||||
utils.OkOrPanic(err)
|
||||
imageClassifier = c
|
||||
}
|
||||
name, _, _ := imageClassifier.PredictFromBytes(imgData)
|
||||
return className == name
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package cookiejar
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"github.com/bytedance/sonic"
|
||||
"io"
|
||||
"likedream-goframe/internal/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type domainMap map[string]cookieMap
|
||||
|
||||
// AllSavePersistCookieJar 完全无限制的 Cookie Jar
|
||||
// 所有域名的 cookies 都保存在一起,访问任何域名都返回所有 cookies
|
||||
// 不检查域名、路径等限制,适用于需要跨域共享 session 的场景
|
||||
type AllSavePersistCookieJar struct {
|
||||
domainMap domainMap
|
||||
*sync.RWMutex
|
||||
}
|
||||
|
||||
const globalDomain = "*" // 使用全局域名存储所有 cookies
|
||||
|
||||
func NewAllSavePersistCookieJar() *AllSavePersistCookieJar {
|
||||
return &AllSavePersistCookieJar{
|
||||
domainMap: make(domainMap),
|
||||
RWMutex: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AllSavePersistCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
// 使用全局域名,所有 cookies 保存在一起
|
||||
if _, exists := a.domainMap[globalDomain]; !exists {
|
||||
a.domainMap[globalDomain] = make(cookieMap)
|
||||
}
|
||||
|
||||
for _, cookie := range cookies {
|
||||
if a.domainMap[globalDomain] == nil {
|
||||
a.domainMap[globalDomain] = make(cookieMap)
|
||||
}
|
||||
|
||||
// 查找是否存在相同 name 的 cookie(忽略 path 和 domain)
|
||||
existingCookies := a.domainMap[globalDomain][cookie.Name]
|
||||
found := false
|
||||
for i, existing := range existingCookies {
|
||||
if existing.Path == cookie.Path && existing.Domain == cookie.Domain {
|
||||
// 找到完全匹配的 cookie,替换它
|
||||
existingCookies[i] = cookie
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
// 没有找到匹配的,添加新的 cookie
|
||||
a.domainMap[globalDomain][cookie.Name] = append(a.domainMap[globalDomain][cookie.Name], cookie)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AllSavePersistCookieJar) Cookies(u *url.URL) []*http.Cookie {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
// 直接返回全局域名下的所有 cookies,不检查域名和路径
|
||||
if cookiesList, exists := a.domainMap[globalDomain]; exists {
|
||||
result := make([]*http.Cookie, 0, len(cookiesList))
|
||||
|
||||
for _, cookies := range cookiesList {
|
||||
// 如果只有一个 cookie,直接返回
|
||||
if len(cookies) == 1 {
|
||||
result = append(result, cookies[0])
|
||||
continue
|
||||
}
|
||||
|
||||
// 有多个同名 cookie,返回最后设置的那个(列表最后一个)
|
||||
if len(cookies) > 0 {
|
||||
result = append(result, cookies[len(cookies)-1])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AllSavePersistCookieJar) Serialize() string {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
json, err := sonic.Marshal(a.domainMap)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
return string(json)
|
||||
}
|
||||
|
||||
func (a *AllSavePersistCookieJar) Deserialize(content string) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
if content == "" {
|
||||
a.domainMap = make(domainMap)
|
||||
return
|
||||
}
|
||||
|
||||
var domainMap domainMap
|
||||
err := sonic.Unmarshal([]byte(content), &domainMap)
|
||||
|
||||
// 如果反序列化失败,尝试从旧格式转换
|
||||
if err != nil {
|
||||
// 旧格式: map[string]map[string]*http.Cookie
|
||||
type oldCookieMap map[string]*http.Cookie
|
||||
type oldDomainMap map[string]oldCookieMap
|
||||
|
||||
var oldMap oldDomainMap
|
||||
oldErr := sonic.Unmarshal([]byte(content), &oldMap)
|
||||
if oldErr != nil {
|
||||
// 两种格式都失败,抛出原始错误
|
||||
utils.OkOrPanic(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 转换旧格式到新格式
|
||||
domainMap = make(map[string]cookieMap)
|
||||
for domain, cookies := range oldMap {
|
||||
domainMap[domain] = make(map[string][]*http.Cookie)
|
||||
for name, cookie := range cookies {
|
||||
domainMap[domain][name] = []*http.Cookie{cookie}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.domainMap = domainMap
|
||||
}
|
||||
|
||||
func (a *AllSavePersistCookieJar) SerializeBytes() []byte {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
ret := make([]byte, 0)
|
||||
retWriter := bytes.NewBuffer(ret)
|
||||
|
||||
err := gob.NewEncoder(retWriter).
|
||||
Encode(a.domainMap)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
return retWriter.Bytes()
|
||||
}
|
||||
|
||||
func (a *AllSavePersistCookieJar) DeserializeBytes(content []byte) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
if len(content) == 0 {
|
||||
a.domainMap = make(domainMap)
|
||||
return
|
||||
}
|
||||
|
||||
retReader := bytes.NewBuffer(content)
|
||||
|
||||
var domainMap domainMap
|
||||
err := gob.NewDecoder(retReader).
|
||||
Decode(&domainMap)
|
||||
|
||||
// 如果反序列化失败,尝试从旧格式转换
|
||||
if err != nil {
|
||||
// 旧格式: map[string]map[string]*http.Cookie
|
||||
type oldCookieMap map[string]*http.Cookie
|
||||
type oldDomainMap map[string]oldCookieMap
|
||||
|
||||
retReader = bytes.NewBuffer(content)
|
||||
var oldMap oldDomainMap
|
||||
oldErr := gob.NewDecoder(retReader).Decode(&oldMap)
|
||||
if oldErr != nil {
|
||||
// 两种格式都失败,抛出原始错误
|
||||
utils.OkOrPanic(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 转换旧格式到新格式
|
||||
domainMap = make(map[string]cookieMap)
|
||||
for domain, cookies := range oldMap {
|
||||
domainMap[domain] = make(map[string][]*http.Cookie)
|
||||
for name, cookie := range cookies {
|
||||
domainMap[domain][name] = []*http.Cookie{cookie}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.domainMap = domainMap
|
||||
}
|
||||
|
||||
func (a *AllSavePersistCookieJar) DeserializeReader(reader io.Reader) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
// 先读取所有数据,以便在失败时可以重试
|
||||
data, err := io.ReadAll(reader)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
var domainMap domainMap
|
||||
err = gob.NewDecoder(bytes.NewBuffer(data)).Decode(&domainMap)
|
||||
|
||||
// 如果反序列化失败,尝试从旧格式转换
|
||||
if err != nil {
|
||||
// 旧格式: map[string]map[string]*http.Cookie
|
||||
type oldCookieMap map[string]*http.Cookie
|
||||
type oldDomainMap map[string]oldCookieMap
|
||||
|
||||
var oldMap oldDomainMap
|
||||
oldErr := gob.NewDecoder(bytes.NewBuffer(data)).Decode(&oldMap)
|
||||
if oldErr != nil {
|
||||
// 两种格式都失败,抛出原始错误
|
||||
utils.OkOrPanic(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 转换旧格式到新格式
|
||||
domainMap = make(map[string]cookieMap)
|
||||
for domain, cookies := range oldMap {
|
||||
domainMap[domain] = make(map[string][]*http.Cookie)
|
||||
for name, cookie := range cookies {
|
||||
domainMap[domain][name] = []*http.Cookie{cookie}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.domainMap = domainMap
|
||||
}
|
||||
@@ -3,17 +3,19 @@ package cookiejar
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"github.com/bytedance/sonic"
|
||||
"io"
|
||||
"likedream-goframe/internal/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
type hostMap map[string]cookieMap
|
||||
|
||||
type cookieMap map[string]*http.Cookie
|
||||
type cookieMap map[string][]*http.Cookie
|
||||
type PersistCookieJar struct {
|
||||
hostMap hostMap
|
||||
*sync.RWMutex
|
||||
@@ -33,14 +35,35 @@ func (p *PersistCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
|
||||
host := u.Host
|
||||
|
||||
if _, exists := p.hostMap[host]; !exists {
|
||||
p.hostMap = make(hostMap)
|
||||
p.hostMap[host] = make(cookieMap)
|
||||
}
|
||||
|
||||
for _, cookie := range cookies {
|
||||
if p.hostMap[host] == nil {
|
||||
p.hostMap[host] = make(cookieMap)
|
||||
}
|
||||
p.hostMap[host][cookie.Name] = cookie
|
||||
|
||||
// 检查并删除全局 cookie 中同名的 cookie
|
||||
if globalCookies, exists := p.hostMap[""]; exists {
|
||||
delete(globalCookies, cookie.Name)
|
||||
}
|
||||
|
||||
// 查找是否存在相同 name, path 和 domain 的 cookie
|
||||
existingCookies := p.hostMap[host][cookie.Name]
|
||||
found := false
|
||||
for i, existing := range existingCookies {
|
||||
if existing.Path == cookie.Path && existing.Domain == cookie.Domain {
|
||||
// 找到完全匹配的 cookie,替换它
|
||||
existingCookies[i] = cookie
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
// 没有找到匹配的,添加新的 cookie
|
||||
p.hostMap[host][cookie.Name] = append(p.hostMap[host][cookie.Name], cookie)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,14 +73,110 @@ func (p *PersistCookieJar) Cookies(u *url.URL) []*http.Cookie {
|
||||
defer p.RUnlock()
|
||||
|
||||
host := u.Host
|
||||
if cookies, exists := p.hostMap[host]; exists {
|
||||
result := make([]*http.Cookie, 0, len(cookies))
|
||||
for _, cookie := range cookies {
|
||||
result = append(result, cookie)
|
||||
}
|
||||
return result
|
||||
requestPath := u.Path
|
||||
if requestPath == "" {
|
||||
requestPath = "/"
|
||||
}
|
||||
return nil
|
||||
|
||||
result := make([]*http.Cookie, 0)
|
||||
addedNames := make(map[string]bool)
|
||||
|
||||
// 先添加匹配 host 的 cookie
|
||||
if cookiesList, exists := p.hostMap[host]; exists {
|
||||
for cookieName, cookies := range cookiesList {
|
||||
// 如果只有一个 cookie,直接返回
|
||||
if len(cookies) == 1 {
|
||||
result = append(result, cookies[0])
|
||||
addedNames[cookieName] = true
|
||||
continue
|
||||
}
|
||||
|
||||
// 有多个同名 cookie,选择最匹配的(path 最长且匹配请求路径的)
|
||||
var bestMatch *http.Cookie
|
||||
bestMatchLen := -1
|
||||
|
||||
for _, cookie := range cookies {
|
||||
cookiePath := cookie.Path
|
||||
if cookiePath == "" {
|
||||
cookiePath = "/"
|
||||
}
|
||||
|
||||
// 检查 path 是否匹配
|
||||
if pathMatch(requestPath, cookiePath) {
|
||||
if len(cookiePath) > bestMatchLen {
|
||||
bestMatch = cookie
|
||||
bestMatchLen = len(cookiePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找到匹配的,添加到结果中
|
||||
if bestMatch != nil {
|
||||
result = append(result, bestMatch)
|
||||
addedNames[cookieName] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 再添加没有 host 的全局 cookie(避免重复)
|
||||
if globalCookies, exists := p.hostMap[""]; exists {
|
||||
for cookieName, cookies := range globalCookies {
|
||||
// 如果该名称的 cookie 已经从 host 中添加了,跳过
|
||||
if addedNames[cookieName] {
|
||||
continue
|
||||
}
|
||||
|
||||
// 如果只有一个 cookie,直接返回
|
||||
if len(cookies) == 1 {
|
||||
result = append(result, cookies[0])
|
||||
continue
|
||||
}
|
||||
|
||||
// 有多个同名 cookie,选择最匹配的
|
||||
var bestMatch *http.Cookie
|
||||
bestMatchLen := -1
|
||||
|
||||
for _, cookie := range cookies {
|
||||
cookiePath := cookie.Path
|
||||
if cookiePath == "" {
|
||||
cookiePath = "/"
|
||||
}
|
||||
|
||||
if pathMatch(requestPath, cookiePath) {
|
||||
if len(cookiePath) > bestMatchLen {
|
||||
bestMatch = cookie
|
||||
bestMatchLen = len(cookiePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if bestMatch != nil {
|
||||
result = append(result, bestMatch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// pathMatch 检查请求路径是否匹配 cookie 的 path
|
||||
// 根据 RFC 6265 的规则
|
||||
func pathMatch(requestPath, cookiePath string) bool {
|
||||
if requestPath == cookiePath {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(requestPath, cookiePath) {
|
||||
// cookiePath 以 / 结尾,或者 requestPath 的下一个字符是 /
|
||||
if strings.HasSuffix(cookiePath, "/") {
|
||||
return true
|
||||
}
|
||||
if len(requestPath) > len(cookiePath) && requestPath[len(cookiePath)] == '/' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *PersistCookieJar) Serialize() string {
|
||||
@@ -83,7 +202,29 @@ func (p *PersistCookieJar) Deserialize(content string) {
|
||||
var hostMap hostMap
|
||||
err := sonic.Unmarshal([]byte(content), &hostMap)
|
||||
|
||||
utils.OkOrPanic(err)
|
||||
// 如果反序列化失败,尝试从旧格式转换
|
||||
if err != nil {
|
||||
// 旧格式: map[string]map[string]*http.Cookie
|
||||
type oldCookieMap map[string]*http.Cookie
|
||||
type oldHostMap map[string]oldCookieMap
|
||||
|
||||
var oldMap oldHostMap
|
||||
oldErr := sonic.Unmarshal([]byte(content), &oldMap)
|
||||
if oldErr != nil {
|
||||
// 两种格式都失败,抛出原始错误
|
||||
utils.OkOrPanic(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 转换旧格式到新格式
|
||||
hostMap = make(map[string]cookieMap)
|
||||
for host, cookies := range oldMap {
|
||||
hostMap[host] = make(map[string][]*http.Cookie)
|
||||
for name, cookie := range cookies {
|
||||
hostMap[host][name] = []*http.Cookie{cookie}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.hostMap = hostMap
|
||||
}
|
||||
@@ -116,7 +257,31 @@ func (p *PersistCookieJar) DeserializeBytes(content []byte) {
|
||||
var hostMap hostMap
|
||||
err := gob.NewDecoder(retReader).
|
||||
Decode(&hostMap)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
// 如果反序列化失败,尝试从旧格式转换
|
||||
if err != nil {
|
||||
// 旧格式: map[string]map[string]*http.Cookie
|
||||
type oldCookieMap map[string]*http.Cookie
|
||||
type oldHostMap map[string]oldCookieMap
|
||||
|
||||
retReader = bytes.NewBuffer(content)
|
||||
var oldMap oldHostMap
|
||||
oldErr := gob.NewDecoder(retReader).Decode(&oldMap)
|
||||
if oldErr != nil {
|
||||
// 两种格式都失败,抛出原始错误
|
||||
utils.OkOrPanic(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 转换旧格式到新格式
|
||||
hostMap = make(map[string]cookieMap)
|
||||
for host, cookies := range oldMap {
|
||||
hostMap[host] = make(map[string][]*http.Cookie)
|
||||
for name, cookie := range cookies {
|
||||
hostMap[host][name] = []*http.Cookie{cookie}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.hostMap = hostMap
|
||||
}
|
||||
@@ -125,10 +290,63 @@ func (p *PersistCookieJar) DeserializeReader(reader io.Reader) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
var hostMap hostMap
|
||||
err := gob.NewDecoder(reader).
|
||||
Decode(&hostMap)
|
||||
// 先读取所有数据,以便在失败时可以重试
|
||||
data, err := io.ReadAll(reader)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
var hostMap hostMap
|
||||
err = gob.NewDecoder(bytes.NewBuffer(data)).Decode(&hostMap)
|
||||
|
||||
// 如果反序列化失败,尝试从旧格式转换
|
||||
if err != nil {
|
||||
// 旧格式: map[string]map[string]*http.Cookie
|
||||
type oldCookieMap map[string]*http.Cookie
|
||||
type oldHostMap map[string]oldCookieMap
|
||||
|
||||
var oldMap oldHostMap
|
||||
oldErr := gob.NewDecoder(bytes.NewBuffer(data)).Decode(&oldMap)
|
||||
if oldErr != nil {
|
||||
// 两种格式都失败,抛出原始错误
|
||||
utils.OkOrPanic(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 转换旧格式到新格式
|
||||
hostMap = make(map[string]cookieMap)
|
||||
for host, cookies := range oldMap {
|
||||
hostMap[host] = make(map[string][]*http.Cookie)
|
||||
for name, cookie := range cookies {
|
||||
hostMap[host][name] = []*http.Cookie{cookie}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.hostMap = hostMap
|
||||
}
|
||||
|
||||
// RemoveCookieInfo 将所有 host 的 cookie 移除 host 信息,存储为全局 cookie
|
||||
func (p *PersistCookieJar) RemoveCookieInfo() {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
// 确保全局 cookie map 存在
|
||||
if _, exists := p.hostMap[""]; !exists {
|
||||
p.hostMap[""] = make(cookieMap)
|
||||
}
|
||||
|
||||
// 将所有 host 的 cookie 移动到全局
|
||||
for host, hostCookies := range p.hostMap {
|
||||
// 跳过全局 cookie 本身
|
||||
if host == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 将该 host 的所有 cookie 移动到全局
|
||||
for cookieName, cookies := range hostCookies {
|
||||
p.hostMap[""][cookieName] = cookies
|
||||
}
|
||||
|
||||
// 删除该 host 的 cookie
|
||||
delete(p.hostMap, host)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,3 +114,10 @@ func TestPersistCookieJar_DeserializeReader(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeseralizeCookie(t *testing.T) {
|
||||
var str = `{"cdpc.campusphere.net":{"MOD_AUTH_CAS":{"Name":"MOD_AUTH_CAS","Value":"ST-iap:1021988086918169:ST:8d7fe60b-dc7d-4fcd-b26b-5517e3ca1926:20251113162807","Quoted":false,"Path":"/","Domain":"","Expires":"0001-01-01T00:00:00Z","RawExpires":"","MaxAge":0,"Secure":false,"HttpOnly":true,"SameSite":0,"Partitioned":false,"Raw":"MOD_AUTH_CAS=ST-iap:1021988086918169:ST:8d7fe60b-dc7d-4fcd-b26b-5517e3ca1926:20251113162807; path=/; Httponly","Unparsed":null},"HWWAFSESID":{"Name":"HWWAFSESID","Value":"88d84731cf506df0bf","Quoted":false,"Path":"/","Domain":"","Expires":"0001-01-01T00:00:00Z","RawExpires":"","MaxAge":0,"Secure":false,"HttpOnly":false,"SameSite":0,"Partitioned":false,"Raw":"HWWAFSESID=88d84731cf506df0bf; path=/","Unparsed":null},"HWWAFSESTIME":{"Name":"HWWAFSESTIME","Value":"1763022486728","Quoted":false,"Path":"/","Domain":"","Expires":"0001-01-01T00:00:00Z","RawExpires":"","MaxAge":0,"Secure":false,"HttpOnly":false,"SameSite":0,"Partitioned":false,"Raw":"HWWAFSESTIME=1763022486728; path=/","Unparsed":null},"CONVERSATION":{"Name":"CONVERSATION","Value":"iap-1021988086918169-CONV-83bee566-7010-4ef6-ad17-4b9eebcccdf8","Quoted":false,"Path":"/iap","Domain":"","Expires":"0001-01-01T00:00:00Z","RawExpires":"","MaxAge":0,"Secure":false,"HttpOnly":true,"SameSite":0,"Partitioned":false,"Raw":"CONVERSATION=iap-1021988086918169-CONV-83bee566-7010-4ef6-ad17-4b9eebcccdf8; Path=/iap; HttpOnly","Unparsed":null},"CASTGC":{"Name":"CASTGC","Value":"iap-1021988086918169-TGT-717aa5db-92cb-46ac-a178-64b630946f43","Quoted":false,"Path":"/iap","Domain":"","Expires":"0001-01-01T00:00:00Z","RawExpires":"","MaxAge":0,"Secure":true,"HttpOnly":true,"SameSite":4,"Partitioned":false,"Raw":"CASTGC=iap-1021988086918169-TGT-717aa5db-92cb-46ac-a178-64b630946f43; Path=/iap; HttpOnly; Secure; SameSite=None","Unparsed":null}}}`
|
||||
jar := NewPersistCookieJar()
|
||||
jar.Deserialize(str)
|
||||
t.Log(jar)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package cryptoutil
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAesDecrypt(t *testing.T) {
|
||||
str := "szFn6zAbjjU="
|
||||
decodeString, _ := base64.StdEncoding.DecodeString(str)
|
||||
decrypt := AesDecrypt(decodeString)
|
||||
t.Log(string(decrypt))
|
||||
encrypt := AesEncrypt([]byte("c9fe4f05-e1ed-4c15-aeb4-b05fa9bfb16f"))
|
||||
t.Log(base64.StdEncoding.EncodeToString(encrypt))
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package cryptoutil
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
)
|
||||
|
||||
func TestDesDecrypt(t *testing.T) {
|
||||
decodeString, _ := base64.StdEncoding.DecodeString("64JITpWPkKuGMhi1m7Ikxu1ekL6VQOdjmJvaQPI07MWbLETtJsxzkk2bCUVNaWgUULa3wS9LH+ANO5CUBeOXi02CcS/8lN/tYDFYCyuK7dLN+q0fD0gQlUsuHwy/78EkwZa9VrLh4e519vM9AxR2GnK4WGpumeH7TGJjDkLH0AitKfUYQnil9AKZEVgSSkHkg9ko64qj0QJvzmo76zOkMpIb1+1Pn2cO+5soTSl8BCsP/8hOP5HdOMVBCYH++iSK3O5sYz/cwZq/G8aJrqmEFcDHbgTouXyD5kxa4lb72Vw3Ku+eH+3qDwYMYxWrIcAm")
|
||||
decrypt := DesDecrypt(decodeString, Cat)
|
||||
str := "{\"lon\":\"113.045246\",\"lat\":\"28.077659\",\"model\":\"847b5b9c-2d97-41ec-8a5d-e9ac79da090d\",\"appVersion\":\"9.7.2\",\"userId\":\"430181200504150038\",\"systemName\":\"android\",\"deviceId\":\"09E46EC9-57C1-4724-BE53-5879CA80A2A0\",\"cpdailyVersion\":\"9.7.2\",\"systemVersion\":\"iOS 17\"}"
|
||||
encrypt := DesEncrypt([]byte(str), Cat)
|
||||
toString := base64.StdEncoding.EncodeToString(encrypt)
|
||||
t.Log(toString)
|
||||
t.Log(string(decrypt))
|
||||
d2, _ := base64.StdEncoding.DecodeString("86JzX4MBsorI5KasKBHUj6orO1qcPWbo758Ux6G2NUoRuGCoZnE+cJW5slZvsflBrKITPeitF7GTisMNRJxRqlA8cLXAqyo9ZlBPPx0MM3KjILHiA/EvrRrmamo7vi1d/F0ChQc8jLfMgkkcOg4FBmM1H8Yg6KyYdMNN7anIbIdJ+6RPEsakjAIw0r4eawofSh5E0QclDry0v36GK3UA3GDBBfqSub8V5PRL7fQty/R0lx7+mfc2AIXXjUVsv85WQKoyyUwg3siWpQL5S1cbtQNUXClSQP1IAGROnBftvdrib01PR1h7Cp+kvE3HK0+/8l0FoGJ1aijff1t9T9MaVXM/jSUFwkA7tmZIjodrUDhZcCMNm5YLm1ZO4UVBQO118FyOBjSLet0nPc0Wg0/dTO0XZoo9e2bCQwjIQC2G8TdzHDxvDjGEc6hX++nZrN23n19bAgqdvzZt58KftznE9s5t4CEjiHx3ykyidGEGr/VEi/05uOtm8/EsFUVp8+MyVgaTxoaq/4LBu28L59U6+6mZjOFUt49STR5p3UPI3ZJQLe2lmnx9BdqrIHbdXgYNu5oNzrDM8m3z3ns9M2HEGg==")
|
||||
d := AesDecryptForCat(d2)
|
||||
t.Log(d)
|
||||
|
||||
form := map[string]*gvar.Var{
|
||||
"appVersion": gvar.New("9.7.2"),
|
||||
"bodyString": gvar.New("0u7hBd+6O11ZaGsT51FUAVfB72waqtTx5oQQLVEEAuCM29x8lQc41i9/dF6+9wjQNC05jdJTBmHlwFoLD09hfsnQWSyjl/ys124RUQL3r/Um5AwNdKiJE24YQsyQlP3ZamUIFlrNSxmn/DfZ/jR9oZhiVKZSlygA2LNLxtNOyk4rETfqKpZKFOnThbXy5Hf779jUDmPNwn1ZrdWN8UHm6wO+EahrrX60vC02VUHs7r3rMrDWZjBJ57eSgD0QwZabcIb/u9kx1AD8OAxINar3X+N+uAhfrxjHd6v6OVvnc2I9iPLEGNFRKZ94jq64hnATOSH6S/IR/QbDoxebWpGUXm3ALYZeoni09uyhG7IE5NtRw/OmqYtdknrs+c0qyOOBkvxPlqKo+SV4qq7xtrn9RWeZIigiyM8mVtdovN1T/x3DnHxd/AYoSS3/cGkg+2QHzWey/Xf5c4g0EvhaGlE0C7ejKIn5wp0XeHyvAjm4ZVyUhgTN2dZVCQ6ChxXOpLnVDWPNeeZ9vysqVl1ZSkQdCz20gy3p9RfYuArluQqjwIoWQe632Cc/Ep++99V5Tc9iIlLr93PfxFaJJfov3avLKw=="),
|
||||
"calVersion": gvar.New("firstv"),
|
||||
"deviceId": gvar.New("09E46EC9-57C1-4724-BE53-5879CA80A2A0"),
|
||||
"lat": gvar.New("28.077659"),
|
||||
"lon": gvar.New("113.045246"),
|
||||
"model": gvar.New("847b5b9c-2d97-41ec-8a5d-e9ac79da090d"),
|
||||
"systemName": gvar.New("android"),
|
||||
"systemVersion": gvar.New("iOS 17"),
|
||||
"userId": gvar.New("430181200504150038"),
|
||||
"version": gvar.New("first_v4"),
|
||||
}
|
||||
sign := GetSubmitSign(form)
|
||||
t.Log(sign)
|
||||
}
|
||||
|
||||
const submitSignSalt = "hru89rwr87Nthifa" // 请根据实际情况替换为真实盐值
|
||||
|
||||
func GetSubmitSign(form map[string]*gvar.Var) string {
|
||||
// 按照指定顺序构建哈希数据
|
||||
keys := []string{
|
||||
"appVersion",
|
||||
"bodyString",
|
||||
"deviceId",
|
||||
"lat",
|
||||
"lon",
|
||||
"model",
|
||||
"systemName",
|
||||
"systemVersion",
|
||||
"userId",
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
for _, key := range keys {
|
||||
val := ""
|
||||
if v, ok := form[key]; ok && v != nil {
|
||||
val = v.String()
|
||||
}
|
||||
// 这里是 key=UrlEncode(key)&,注意是对 key 进行 url encode
|
||||
sb.WriteString(key)
|
||||
sb.WriteString("=")
|
||||
sb.WriteString(url.QueryEscape(val))
|
||||
sb.WriteString("&")
|
||||
}
|
||||
sb.WriteString(submitSignSalt)
|
||||
|
||||
// 计算MD5
|
||||
hash := md5.Sum([]byte(sb.String()))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package cryptoutil
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// MD5 对输入字符串进行 MD5 加密,返回 32 位小写十六进制字符串
|
||||
func MD5(input string) string {
|
||||
hash := md5.Sum([]byte(input))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
@@ -0,0 +1,838 @@
|
||||
package cryptoutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RSA Constants
|
||||
const (
|
||||
biRadixBits = 16
|
||||
bitsPerDigit = biRadixBits
|
||||
biRadix = 1 << 16 // 65536
|
||||
biHalfRadix = biRadix >> 1
|
||||
biRadixSquared = biRadix * biRadix
|
||||
maxDigitVal = biRadix - 1
|
||||
maxInteger = 9999999999999998
|
||||
dpl10 = 15
|
||||
)
|
||||
|
||||
var (
|
||||
maxDigits int
|
||||
zeroArray []int
|
||||
bigZero *BigInt
|
||||
bigOne *BigInt
|
||||
lr10 *BigInt
|
||||
highBitMasks = []int{0x0000, 0x8000, 0xC000, 0xE000, 0xF000, 0xF800,
|
||||
0xFC00, 0xFE00, 0xFF00, 0xFF80, 0xFFC0, 0xFFE0,
|
||||
0xFFF0, 0xFFF8, 0xFFFC, 0xFFFE, 0xFFFF}
|
||||
lowBitMasks = []int{0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F,
|
||||
0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF,
|
||||
0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF}
|
||||
hexToChar = []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'a', 'b', 'c', 'd', 'e', 'f'}
|
||||
hexatrigesimalToChar = []byte{
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
|
||||
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
|
||||
'u', 'v', 'w', 'x', 'y', 'z'}
|
||||
)
|
||||
|
||||
// BigInt represents a big integer
|
||||
type BigInt struct {
|
||||
digits []int
|
||||
isNeg bool
|
||||
}
|
||||
|
||||
// NewBigInt creates a new BigInt
|
||||
func NewBigInt() *BigInt {
|
||||
digits := make([]int, len(zeroArray))
|
||||
copy(digits, zeroArray)
|
||||
return &BigInt{
|
||||
digits: digits,
|
||||
isNeg: false,
|
||||
}
|
||||
}
|
||||
|
||||
// NewBigIntEmpty creates an empty BigInt
|
||||
func NewBigIntEmpty() *BigInt {
|
||||
return &BigInt{
|
||||
digits: nil,
|
||||
isNeg: false,
|
||||
}
|
||||
}
|
||||
|
||||
// SetMaxDigits sets the maximum number of digits
|
||||
func SetMaxDigits(value int) {
|
||||
maxDigits = value
|
||||
zeroArray = make([]int, maxDigits)
|
||||
bigZero = NewBigInt()
|
||||
bigOne = NewBigInt()
|
||||
bigOne.digits[0] = 1
|
||||
lr10 = BiFromNumber(1000000000000000)
|
||||
}
|
||||
|
||||
// BiFromNumber creates a BigInt from a number
|
||||
func BiFromNumber(i int64) *BigInt {
|
||||
result := NewBigInt()
|
||||
result.isNeg = i < 0
|
||||
i = int64(math.Abs(float64(i)))
|
||||
j := 0
|
||||
for i > 0 {
|
||||
result.digits[j] = int(i & maxDigitVal)
|
||||
j++
|
||||
i = i / biRadix
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BiFromDecimal creates a BigInt from a decimal string
|
||||
func BiFromDecimal(s string) *BigInt {
|
||||
isNeg := len(s) > 0 && s[0] == '-'
|
||||
i := 0
|
||||
if isNeg {
|
||||
i = 1
|
||||
}
|
||||
|
||||
// Skip leading zeros
|
||||
for i < len(s) && s[i] == '0' {
|
||||
i++
|
||||
}
|
||||
|
||||
if i == len(s) {
|
||||
return NewBigInt()
|
||||
}
|
||||
|
||||
digitCount := len(s) - i
|
||||
fgl := digitCount % dpl10
|
||||
if fgl == 0 {
|
||||
fgl = dpl10
|
||||
}
|
||||
|
||||
num := parseNumber(s[i : i+fgl])
|
||||
result := BiFromNumber(num)
|
||||
i += fgl
|
||||
|
||||
for i < len(s) {
|
||||
result = BiAdd(BiMultiply(result, lr10), BiFromNumber(parseNumber(s[i:min(i+dpl10, len(s))])))
|
||||
i += dpl10
|
||||
}
|
||||
|
||||
result.isNeg = isNeg
|
||||
return result
|
||||
}
|
||||
|
||||
// BiCopy copies a BigInt
|
||||
func BiCopy(bi *BigInt) *BigInt {
|
||||
result := NewBigIntEmpty()
|
||||
result.digits = make([]int, len(bi.digits))
|
||||
copy(result.digits, bi.digits)
|
||||
result.isNeg = bi.isNeg
|
||||
return result
|
||||
}
|
||||
|
||||
// ReverseStr reverses a string
|
||||
func ReverseStr(s string) string {
|
||||
runes := []rune(s)
|
||||
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
||||
runes[i], runes[j] = runes[j], runes[i]
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
// BiToString converts BigInt to string with given radix
|
||||
func BiToString(x *BigInt, radix int) string {
|
||||
b := NewBigInt()
|
||||
b.digits[0] = radix
|
||||
qr := BiDivideModulo(x, b)
|
||||
result := string(hexatrigesimalToChar[qr[1].digits[0]])
|
||||
|
||||
for BiCompare(qr[0], bigZero) == 1 {
|
||||
qr = BiDivideModulo(qr[0], b)
|
||||
result += string(hexatrigesimalToChar[qr[1].digits[0]])
|
||||
}
|
||||
|
||||
prefix := ""
|
||||
if x.isNeg {
|
||||
prefix = "-"
|
||||
}
|
||||
return prefix + ReverseStr(result)
|
||||
}
|
||||
|
||||
// BiToDecimal converts BigInt to decimal string
|
||||
func BiToDecimal(x *BigInt) string {
|
||||
b := NewBigInt()
|
||||
b.digits[0] = 10
|
||||
qr := BiDivideModulo(x, b)
|
||||
result := fmt.Sprintf("%d", qr[1].digits[0])
|
||||
|
||||
for BiCompare(qr[0], bigZero) == 1 {
|
||||
qr = BiDivideModulo(qr[0], b)
|
||||
result += fmt.Sprintf("%d", qr[1].digits[0])
|
||||
}
|
||||
|
||||
prefix := ""
|
||||
if x.isNeg {
|
||||
prefix = "-"
|
||||
}
|
||||
return prefix + ReverseStr(result)
|
||||
}
|
||||
|
||||
// DigitToHex converts a digit to hex string
|
||||
func DigitToHex(n int) string {
|
||||
mask := 0xf
|
||||
result := ""
|
||||
for i := 0; i < 4; i++ {
|
||||
result += string(hexToChar[n&mask])
|
||||
n >>= 4
|
||||
}
|
||||
return ReverseStr(result)
|
||||
}
|
||||
|
||||
// BiToHex converts BigInt to hex string
|
||||
func BiToHex(x *BigInt) string {
|
||||
result := ""
|
||||
for i := BiHighIndex(x); i > -1; i-- {
|
||||
result += DigitToHex(x.digits[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// CharToHex converts a character code to hex value
|
||||
func CharToHex(c byte) int {
|
||||
const (
|
||||
ZERO = 48
|
||||
NINE = ZERO + 9
|
||||
littleA = 97
|
||||
littleZ = littleA + 25
|
||||
bigA = 65
|
||||
bigZ = 65 + 25
|
||||
)
|
||||
|
||||
if c >= ZERO && c <= NINE {
|
||||
return int(c - ZERO)
|
||||
} else if c >= bigA && c <= bigZ {
|
||||
return 10 + int(c-bigA)
|
||||
} else if c >= littleA && c <= littleZ {
|
||||
return 10 + int(c-littleA)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// HexToDigit converts hex string to digit
|
||||
func HexToDigit(s string) int {
|
||||
result := 0
|
||||
sl := min(len(s), 4)
|
||||
for i := 0; i < sl; i++ {
|
||||
result <<= 4
|
||||
result |= CharToHex(s[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BiFromHex creates BigInt from hex string
|
||||
func BiFromHex(s string) *BigInt {
|
||||
result := NewBigInt()
|
||||
sl := len(s)
|
||||
for i, j := sl, 0; i > 0; i, j = i-4, j+1 {
|
||||
start := max(i-4, 0)
|
||||
length := min(i, 4)
|
||||
if start+length > sl {
|
||||
length = sl - start
|
||||
}
|
||||
result.digits[j] = HexToDigit(s[start : start+length])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BiFromString creates BigInt from string with given radix
|
||||
func BiFromString(s string, radix int) *BigInt {
|
||||
isNeg := len(s) > 0 && s[0] == '-'
|
||||
istop := 0
|
||||
if isNeg {
|
||||
istop = 1
|
||||
}
|
||||
|
||||
result := NewBigInt()
|
||||
place := NewBigInt()
|
||||
place.digits[0] = 1
|
||||
|
||||
for i := len(s) - 1; i >= istop; i-- {
|
||||
c := s[i]
|
||||
digit := CharToHex(c)
|
||||
biDigit := BiMultiplyDigit(place, digit)
|
||||
result = BiAdd(result, biDigit)
|
||||
place = BiMultiplyDigit(place, radix)
|
||||
}
|
||||
|
||||
result.isNeg = isNeg
|
||||
return result
|
||||
}
|
||||
|
||||
// BiAdd adds two BigInts
|
||||
func BiAdd(x, y *BigInt) *BigInt {
|
||||
var result *BigInt
|
||||
|
||||
if x.isNeg != y.isNeg {
|
||||
y.isNeg = !y.isNeg
|
||||
result = BiSubtract(x, y)
|
||||
y.isNeg = !y.isNeg
|
||||
} else {
|
||||
result = NewBigInt()
|
||||
c := 0
|
||||
for i := 0; i < len(x.digits); i++ {
|
||||
n := x.digits[i] + y.digits[i] + c
|
||||
result.digits[i] = n % biRadix
|
||||
if n >= biRadix {
|
||||
c = 1
|
||||
} else {
|
||||
c = 0
|
||||
}
|
||||
}
|
||||
result.isNeg = x.isNeg
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// BiSubtract subtracts two BigInts
|
||||
func BiSubtract(x, y *BigInt) *BigInt {
|
||||
var result *BigInt
|
||||
|
||||
if x.isNeg != y.isNeg {
|
||||
y.isNeg = !y.isNeg
|
||||
result = BiAdd(x, y)
|
||||
y.isNeg = !y.isNeg
|
||||
} else {
|
||||
result = NewBigInt()
|
||||
c := 0
|
||||
|
||||
for i := 0; i < len(x.digits); i++ {
|
||||
n := x.digits[i] - y.digits[i] + c
|
||||
result.digits[i] = n % biRadix
|
||||
if result.digits[i] < 0 {
|
||||
result.digits[i] += biRadix
|
||||
}
|
||||
if n < 0 {
|
||||
c = -1
|
||||
} else {
|
||||
c = 0
|
||||
}
|
||||
}
|
||||
|
||||
if c == -1 {
|
||||
c = 0
|
||||
for i := 0; i < len(x.digits); i++ {
|
||||
n := 0 - result.digits[i] + c
|
||||
result.digits[i] = n % biRadix
|
||||
if result.digits[i] < 0 {
|
||||
result.digits[i] += biRadix
|
||||
}
|
||||
if n < 0 {
|
||||
c = -1
|
||||
} else {
|
||||
c = 0
|
||||
}
|
||||
}
|
||||
result.isNeg = !x.isNeg
|
||||
} else {
|
||||
result.isNeg = x.isNeg
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// BiHighIndex returns the highest non-zero digit index
|
||||
func BiHighIndex(x *BigInt) int {
|
||||
result := len(x.digits) - 1
|
||||
for result > 0 && x.digits[result] == 0 {
|
||||
result--
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BiNumBits returns the number of bits
|
||||
func BiNumBits(x *BigInt) int {
|
||||
n := BiHighIndex(x)
|
||||
d := x.digits[n]
|
||||
m := (n + 1) * bitsPerDigit
|
||||
result := m
|
||||
for result > m-bitsPerDigit {
|
||||
if (d & 0x8000) != 0 {
|
||||
break
|
||||
}
|
||||
d <<= 1
|
||||
result--
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BiMultiply multiplies two BigInts
|
||||
func BiMultiply(x, y *BigInt) *BigInt {
|
||||
result := NewBigInt()
|
||||
n := BiHighIndex(x)
|
||||
t := BiHighIndex(y)
|
||||
|
||||
for i := 0; i <= t; i++ {
|
||||
c := 0
|
||||
k := i
|
||||
for j := 0; j <= n; j, k = j+1, k+1 {
|
||||
uv := result.digits[k] + x.digits[j]*y.digits[i] + c
|
||||
result.digits[k] = uv & maxDigitVal
|
||||
c = uv >> biRadixBits
|
||||
}
|
||||
result.digits[i+n+1] = c
|
||||
}
|
||||
|
||||
result.isNeg = x.isNeg != y.isNeg
|
||||
return result
|
||||
}
|
||||
|
||||
// BiMultiplyDigit multiplies BigInt by a digit
|
||||
func BiMultiplyDigit(x *BigInt, y int) *BigInt {
|
||||
result := NewBigInt()
|
||||
n := BiHighIndex(x)
|
||||
c := 0
|
||||
|
||||
for j := 0; j <= n; j++ {
|
||||
uv := result.digits[j] + x.digits[j]*y + c
|
||||
result.digits[j] = uv & maxDigitVal
|
||||
c = uv >> biRadixBits
|
||||
}
|
||||
result.digits[1+n] = c
|
||||
return result
|
||||
}
|
||||
|
||||
// ArrayCopy copies array elements
|
||||
func ArrayCopy(src []int, srcStart int, dest []int, destStart int, n int) {
|
||||
m := min(srcStart+n, len(src))
|
||||
for i, j := srcStart, destStart; i < m; i, j = i+1, j+1 {
|
||||
dest[j] = src[i]
|
||||
}
|
||||
}
|
||||
|
||||
// BiShiftLeft shifts BigInt left by n bits
|
||||
func BiShiftLeft(x *BigInt, n int) *BigInt {
|
||||
digitCount := n / bitsPerDigit
|
||||
result := NewBigInt()
|
||||
ArrayCopy(x.digits, 0, result.digits, digitCount, len(result.digits)-digitCount)
|
||||
|
||||
bits := n % bitsPerDigit
|
||||
rightBits := bitsPerDigit - bits
|
||||
|
||||
for i, i1 := len(result.digits)-1, len(result.digits)-2; i > 0; i, i1 = i-1, i1-1 {
|
||||
result.digits[i] = ((result.digits[i] << bits) & maxDigitVal) |
|
||||
((result.digits[i1] & highBitMasks[bits]) >> rightBits)
|
||||
}
|
||||
result.digits[0] = ((result.digits[0] << bits) & maxDigitVal)
|
||||
result.isNeg = x.isNeg
|
||||
return result
|
||||
}
|
||||
|
||||
// BiShiftRight shifts BigInt right by n bits
|
||||
func BiShiftRight(x *BigInt, n int) *BigInt {
|
||||
digitCount := n / bitsPerDigit
|
||||
result := NewBigInt()
|
||||
ArrayCopy(x.digits, digitCount, result.digits, 0, len(x.digits)-digitCount)
|
||||
|
||||
bits := n % bitsPerDigit
|
||||
leftBits := bitsPerDigit - bits
|
||||
|
||||
for i, i1 := 0, 1; i < len(result.digits)-1; i, i1 = i+1, i1+1 {
|
||||
result.digits[i] = (result.digits[i] >> bits) |
|
||||
((result.digits[i1] & lowBitMasks[bits]) << leftBits)
|
||||
}
|
||||
result.digits[len(result.digits)-1] >>= bits
|
||||
result.isNeg = x.isNeg
|
||||
return result
|
||||
}
|
||||
|
||||
// BiMultiplyByRadixPower multiplies BigInt by radix^n
|
||||
func BiMultiplyByRadixPower(x *BigInt, n int) *BigInt {
|
||||
result := NewBigInt()
|
||||
ArrayCopy(x.digits, 0, result.digits, n, len(result.digits)-n)
|
||||
return result
|
||||
}
|
||||
|
||||
// BiDivideByRadixPower divides BigInt by radix^n
|
||||
func BiDivideByRadixPower(x *BigInt, n int) *BigInt {
|
||||
result := NewBigInt()
|
||||
ArrayCopy(x.digits, n, result.digits, 0, len(result.digits)-n)
|
||||
return result
|
||||
}
|
||||
|
||||
// BiModuloByRadixPower computes BigInt mod radix^n
|
||||
func BiModuloByRadixPower(x *BigInt, n int) *BigInt {
|
||||
result := NewBigInt()
|
||||
ArrayCopy(x.digits, 0, result.digits, 0, n)
|
||||
return result
|
||||
}
|
||||
|
||||
// BiCompare compares two BigInts
|
||||
func BiCompare(x, y *BigInt) int {
|
||||
if x.isNeg != y.isNeg {
|
||||
if x.isNeg {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
for i := len(x.digits) - 1; i >= 0; i-- {
|
||||
if x.digits[i] != y.digits[i] {
|
||||
if x.isNeg {
|
||||
if x.digits[i] > y.digits[i] {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
} else {
|
||||
if x.digits[i] < y.digits[i] {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// BiDivideModulo divides two BigInts and returns quotient and remainder
|
||||
func BiDivideModulo(x, y *BigInt) []*BigInt {
|
||||
nb := BiNumBits(x)
|
||||
tb := BiNumBits(y)
|
||||
origYIsNeg := y.isNeg
|
||||
var q, r *BigInt
|
||||
|
||||
if nb < tb {
|
||||
if x.isNeg {
|
||||
q = BiCopy(bigOne)
|
||||
q.isNeg = !y.isNeg
|
||||
x.isNeg = false
|
||||
y.isNeg = false
|
||||
r = BiSubtract(y, x)
|
||||
x.isNeg = true
|
||||
y.isNeg = origYIsNeg
|
||||
} else {
|
||||
q = NewBigInt()
|
||||
r = BiCopy(x)
|
||||
}
|
||||
return []*BigInt{q, r}
|
||||
}
|
||||
|
||||
q = NewBigInt()
|
||||
r = x
|
||||
|
||||
t := int(math.Ceil(float64(tb)/float64(bitsPerDigit))) - 1
|
||||
lambda := 0
|
||||
for y.digits[t] < biHalfRadix {
|
||||
y = BiShiftLeft(y, 1)
|
||||
lambda++
|
||||
tb++
|
||||
t = int(math.Ceil(float64(tb)/float64(bitsPerDigit))) - 1
|
||||
}
|
||||
|
||||
r = BiShiftLeft(r, lambda)
|
||||
nb += lambda
|
||||
n := int(math.Ceil(float64(nb)/float64(bitsPerDigit))) - 1
|
||||
|
||||
b := BiMultiplyByRadixPower(y, n-t)
|
||||
for BiCompare(r, b) != -1 {
|
||||
q.digits[n-t]++
|
||||
r = BiSubtract(r, b)
|
||||
}
|
||||
|
||||
for i := n; i > t; i-- {
|
||||
ri := 0
|
||||
if i < len(r.digits) {
|
||||
ri = r.digits[i]
|
||||
}
|
||||
ri1 := 0
|
||||
if i-1 < len(r.digits) {
|
||||
ri1 = r.digits[i-1]
|
||||
}
|
||||
ri2 := 0
|
||||
if i-2 < len(r.digits) {
|
||||
ri2 = r.digits[i-2]
|
||||
}
|
||||
yt := 0
|
||||
if t < len(y.digits) {
|
||||
yt = y.digits[t]
|
||||
}
|
||||
yt1 := 0
|
||||
if t-1 < len(y.digits) {
|
||||
yt1 = y.digits[t-1]
|
||||
}
|
||||
|
||||
if ri == yt {
|
||||
q.digits[i-t-1] = maxDigitVal
|
||||
} else {
|
||||
q.digits[i-t-1] = (ri*biRadix + ri1) / yt
|
||||
}
|
||||
|
||||
c1 := q.digits[i-t-1] * ((yt * biRadix) + yt1)
|
||||
c2 := (ri * biRadixSquared) + ((ri1 * biRadix) + ri2)
|
||||
|
||||
for c1 > c2 {
|
||||
q.digits[i-t-1]--
|
||||
c1 = q.digits[i-t-1] * ((yt * biRadix) | yt1)
|
||||
c2 = (ri * biRadix * biRadix) + ((ri1 * biRadix) + ri2)
|
||||
}
|
||||
|
||||
b = BiMultiplyByRadixPower(y, i-t-1)
|
||||
r = BiSubtract(r, BiMultiplyDigit(b, q.digits[i-t-1]))
|
||||
if r.isNeg {
|
||||
r = BiAdd(r, b)
|
||||
q.digits[i-t-1]--
|
||||
}
|
||||
}
|
||||
|
||||
r = BiShiftRight(r, lambda)
|
||||
q.isNeg = x.isNeg != origYIsNeg
|
||||
|
||||
if x.isNeg {
|
||||
if origYIsNeg {
|
||||
q = BiAdd(q, bigOne)
|
||||
} else {
|
||||
q = BiSubtract(q, bigOne)
|
||||
}
|
||||
y = BiShiftRight(y, lambda)
|
||||
r = BiSubtract(y, r)
|
||||
}
|
||||
|
||||
if r.digits[0] == 0 && BiHighIndex(r) == 0 {
|
||||
r.isNeg = false
|
||||
}
|
||||
|
||||
return []*BigInt{q, r}
|
||||
}
|
||||
|
||||
// BiDivide divides two BigInts
|
||||
func BiDivide(x, y *BigInt) *BigInt {
|
||||
return BiDivideModulo(x, y)[0]
|
||||
}
|
||||
|
||||
// BiModulo computes x mod y
|
||||
func BiModulo(x, y *BigInt) *BigInt {
|
||||
return BiDivideModulo(x, y)[1]
|
||||
}
|
||||
|
||||
// BiMultiplyMod computes (x * y) mod m
|
||||
func BiMultiplyMod(x, y, m *BigInt) *BigInt {
|
||||
return BiModulo(BiMultiply(x, y), m)
|
||||
}
|
||||
|
||||
// BiPowMod computes x^y mod m
|
||||
func BiPowMod(x, y, m *BigInt) *BigInt {
|
||||
result := BiCopy(bigOne)
|
||||
a := x
|
||||
k := y
|
||||
|
||||
for {
|
||||
if (k.digits[0] & 1) != 0 {
|
||||
result = BiMultiplyMod(result, a, m)
|
||||
}
|
||||
k = BiShiftRight(k, 1)
|
||||
if k.digits[0] == 0 && BiHighIndex(k) == 0 {
|
||||
break
|
||||
}
|
||||
a = BiMultiplyMod(a, a, m)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// BarrettMu represents Barrett reduction
|
||||
type BarrettMu struct {
|
||||
modulus *BigInt
|
||||
k int
|
||||
mu *BigInt
|
||||
bkplus1 *BigInt
|
||||
}
|
||||
|
||||
// NewBarrettMu creates a new BarrettMu
|
||||
func NewBarrettMu(m *BigInt) *BarrettMu {
|
||||
bm := &BarrettMu{
|
||||
modulus: BiCopy(m),
|
||||
k: BiHighIndex(m) + 1,
|
||||
}
|
||||
|
||||
b2k := NewBigInt()
|
||||
if 2*bm.k < len(b2k.digits) {
|
||||
b2k.digits[2*bm.k] = 1
|
||||
}
|
||||
bm.mu = BiDivide(b2k, bm.modulus)
|
||||
|
||||
bm.bkplus1 = NewBigInt()
|
||||
if bm.k+1 < len(bm.bkplus1.digits) {
|
||||
bm.bkplus1.digits[bm.k+1] = 1
|
||||
}
|
||||
|
||||
return bm
|
||||
}
|
||||
|
||||
// Modulo performs Barrett modulo
|
||||
func (bm *BarrettMu) Modulo(x *BigInt) *BigInt {
|
||||
q1 := BiDivideByRadixPower(x, bm.k-1)
|
||||
q2 := BiMultiply(q1, bm.mu)
|
||||
q3 := BiDivideByRadixPower(q2, bm.k+1)
|
||||
r1 := BiModuloByRadixPower(x, bm.k+1)
|
||||
r2term := BiMultiply(q3, bm.modulus)
|
||||
r2 := BiModuloByRadixPower(r2term, bm.k+1)
|
||||
r := BiSubtract(r1, r2)
|
||||
|
||||
if r.isNeg {
|
||||
r = BiAdd(r, bm.bkplus1)
|
||||
}
|
||||
|
||||
for BiCompare(r, bm.modulus) >= 0 {
|
||||
r = BiSubtract(r, bm.modulus)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// MultiplyMod performs modular multiplication
|
||||
func (bm *BarrettMu) MultiplyMod(x, y *BigInt) *BigInt {
|
||||
xy := BiMultiply(x, y)
|
||||
return bm.Modulo(xy)
|
||||
}
|
||||
|
||||
// PowMod performs modular exponentiation
|
||||
func (bm *BarrettMu) PowMod(x, y *BigInt) *BigInt {
|
||||
result := NewBigInt()
|
||||
result.digits[0] = 1
|
||||
a := x
|
||||
k := y
|
||||
|
||||
for {
|
||||
if (k.digits[0] & 1) != 0 {
|
||||
result = bm.MultiplyMod(result, a)
|
||||
}
|
||||
k = BiShiftRight(k, 1)
|
||||
if k.digits[0] == 0 && BiHighIndex(k) == 0 {
|
||||
break
|
||||
}
|
||||
a = bm.MultiplyMod(a, a)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// NutRSAKeyPair represents an RSA key pair
|
||||
type NutRSAKeyPair struct {
|
||||
e *BigInt
|
||||
d *BigInt
|
||||
m *BigInt
|
||||
chunkSize int
|
||||
radix int
|
||||
barrett *BarrettMu
|
||||
}
|
||||
|
||||
// NewNutRSAKeyPair creates a new RSA key pair
|
||||
func NewNutRSAKeyPair(encryptionExponent, decryptionExponent, modulus string) *NutRSAKeyPair {
|
||||
kp := &NutRSAKeyPair{
|
||||
e: BiFromHex(encryptionExponent),
|
||||
d: BiFromHex(decryptionExponent),
|
||||
m: BiFromHex(modulus),
|
||||
radix: 16,
|
||||
}
|
||||
kp.chunkSize = 2 * BiHighIndex(kp.m)
|
||||
kp.barrett = NewBarrettMu(kp.m)
|
||||
return kp
|
||||
}
|
||||
|
||||
// EncryptedString encrypts a string
|
||||
func (kp *NutRSAKeyPair) EncryptedString(s string) string {
|
||||
a := make([]int, 0)
|
||||
for i := 0; i < len(s); i++ {
|
||||
a = append(a, int(s[i]))
|
||||
}
|
||||
|
||||
// Padding
|
||||
for len(a)%kp.chunkSize != 0 {
|
||||
a = append(a, 0)
|
||||
}
|
||||
|
||||
result := ""
|
||||
for i := 0; i < len(a); i += kp.chunkSize {
|
||||
block := NewBigInt()
|
||||
j := 0
|
||||
for k := i; k < i+kp.chunkSize; j++ {
|
||||
if k < len(a) {
|
||||
block.digits[j] = a[k]
|
||||
k++
|
||||
}
|
||||
if k < len(a) {
|
||||
block.digits[j] += a[k] << 8
|
||||
k++
|
||||
}
|
||||
}
|
||||
crypt := kp.barrett.PowMod(block, kp.e)
|
||||
var text string
|
||||
if kp.radix == 16 {
|
||||
text = BiToHex(crypt)
|
||||
} else {
|
||||
text = BiToString(crypt, kp.radix)
|
||||
}
|
||||
result += text + " "
|
||||
}
|
||||
|
||||
if len(result) > 0 {
|
||||
return result[:len(result)-1]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// DecryptedString decrypts a string
|
||||
func (kp *NutRSAKeyPair) DecryptedString(s string) string {
|
||||
blocks := strings.Split(s, " ")
|
||||
result := ""
|
||||
|
||||
for i := 0; i < len(blocks); i++ {
|
||||
var bi *BigInt
|
||||
if kp.radix == 16 {
|
||||
bi = BiFromHex(blocks[i])
|
||||
} else {
|
||||
bi = BiFromString(blocks[i], kp.radix)
|
||||
}
|
||||
block := kp.barrett.PowMod(bi, kp.d)
|
||||
|
||||
for j := 0; j <= BiHighIndex(block); j++ {
|
||||
result += string(rune(block.digits[j] & 255))
|
||||
result += string(rune(block.digits[j] >> 8))
|
||||
}
|
||||
}
|
||||
|
||||
// Remove trailing null
|
||||
if len(result) > 0 && result[len(result)-1] == 0 {
|
||||
result = result[:len(result)-1]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func parseNumber(s string) int64 {
|
||||
var result int64
|
||||
fmt.Sscanf(s, "%d", &result)
|
||||
return result
|
||||
}
|
||||
|
||||
func init() {
|
||||
SetMaxDigits(130)
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package cryptoutil
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// RSAKeyPair RSA密钥对结构体
|
||||
type RSAKeyPair struct {
|
||||
n *big.Int // 模数
|
||||
e *big.Int // 公钥指数
|
||||
}
|
||||
|
||||
// NewRSAKeyPair 创建RSA密钥对
|
||||
// exponent: 公钥指数的十六进制字符串,通常是 "10001" (65537)
|
||||
// privateExponent: 私钥指数的十六进制字符串,加密时可以为空
|
||||
// modulus: 模数的十六进制字符串
|
||||
func NewRSAKeyPair(exponent, privateExponent, modulus string) (*RSAKeyPair, error) {
|
||||
// 解析公钥指数
|
||||
e := new(big.Int)
|
||||
if _, ok := e.SetString(exponent, 16); !ok {
|
||||
return nil, fmt.Errorf("invalid exponent: %s", exponent)
|
||||
}
|
||||
|
||||
// 解析模数
|
||||
n := new(big.Int)
|
||||
if _, ok := n.SetString(modulus, 16); !ok {
|
||||
return nil, fmt.Errorf("invalid modulus: %s", modulus)
|
||||
}
|
||||
|
||||
return &RSAKeyPair{
|
||||
n: n,
|
||||
e: e,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewRSAKeyPairFromPublicKey 从 X.509 格式的 base64 公钥字符串创建 RSA 密钥对
|
||||
// publicKeyBase64: X.509 DER 格式的 base64 编码公钥字符串
|
||||
func NewRSAKeyPairFromPublicKey(publicKeyBase64 string) (*RSAKeyPair, error) {
|
||||
// 解码 base64
|
||||
derBytes, err := base64.StdEncoding.DecodeString(publicKeyBase64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode base64: %w", err)
|
||||
}
|
||||
|
||||
// 解析 X.509 格式的公钥
|
||||
pubKey, err := x509.ParsePKIXPublicKey(derBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse public key: %w", err)
|
||||
}
|
||||
|
||||
// 转换为 RSA 公钥
|
||||
rsaPubKey, ok := pubKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("not an RSA public key")
|
||||
}
|
||||
|
||||
// 创建密钥对
|
||||
return &RSAKeyPair{
|
||||
n: rsaPubKey.N,
|
||||
e: big.NewInt(int64(rsaPubKey.E)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Encrypt 使用公钥加密数据,返回十六进制字符串
|
||||
func (k *RSAKeyPair) Encrypt(plaintext string) (string, error) {
|
||||
ciphertext, err := k.EncryptBytes([]byte(plaintext))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// 返回十六进制编码的密文
|
||||
return hex.EncodeToString(ciphertext), nil
|
||||
}
|
||||
|
||||
// EncryptBytes 使用公钥加密字节数据,返回加密后的字节
|
||||
// 使用 PKCS1v15 填充方式,兼容 JavaScript RSA 实现
|
||||
func (k *RSAKeyPair) EncryptBytes(plaintext []byte) ([]byte, error) {
|
||||
// 计算密钥长度(字节)
|
||||
keySize := (k.n.BitLen() + 7) / 8
|
||||
|
||||
// PKCS1v15 填充
|
||||
padded, err := pkcs1v15Pad(plaintext, keySize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 将填充后的数据转换为大整数
|
||||
m := new(big.Int).SetBytes(padded)
|
||||
|
||||
// 执行 RSA 加密: c = m^e mod n
|
||||
c := new(big.Int).Exp(m, k.e, k.n)
|
||||
|
||||
// 将结果转换为字节数组,确保长度与密钥长度一致
|
||||
ciphertext := c.Bytes()
|
||||
if len(ciphertext) < keySize {
|
||||
// 如果结果长度不足,在前面填充零
|
||||
result := make([]byte, keySize)
|
||||
copy(result[keySize-len(ciphertext):], ciphertext)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
// pkcs1v15Pad 实现 PKCS#1 v1.5 填充
|
||||
func pkcs1v15Pad(data []byte, keySize int) ([]byte, error) {
|
||||
if len(data) > keySize-11 {
|
||||
return nil, fmt.Errorf("data too long: %d bytes (max %d)", len(data), keySize-11)
|
||||
}
|
||||
|
||||
// PKCS1v15 格式: 0x00 || 0x02 || PS || 0x00 || data
|
||||
// 其中 PS 是随机非零字节,长度至少为 8 字节
|
||||
padLen := keySize - len(data) - 3
|
||||
|
||||
padded := make([]byte, keySize)
|
||||
padded[0] = 0x00
|
||||
padded[1] = 0x02
|
||||
|
||||
// 生成随机非零字节作为填充
|
||||
ps := make([]byte, padLen)
|
||||
_, err := rand.Read(ps)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate random padding: %w", err)
|
||||
}
|
||||
|
||||
// 确保所有填充字节都是非零
|
||||
for i := 0; i < padLen; i++ {
|
||||
for ps[i] == 0 {
|
||||
rand.Read(ps[i : i+1])
|
||||
}
|
||||
padded[2+i] = ps[i]
|
||||
}
|
||||
|
||||
padded[2+padLen] = 0x00
|
||||
copy(padded[3+padLen:], data)
|
||||
|
||||
return padded, nil
|
||||
}
|
||||
|
||||
// GetModulus 获取模数
|
||||
func (k *RSAKeyPair) GetModulus() *big.Int {
|
||||
return k.n
|
||||
}
|
||||
|
||||
// GetExponent 获取公钥指数
|
||||
func (k *RSAKeyPair) GetExponent() *big.Int {
|
||||
return k.e
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package cryptoutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRSAKeyPair(t *testing.T) {
|
||||
// 使用你提供的参数创建 RSA 密钥对
|
||||
exponent := "10001"
|
||||
modulus := "a1155dc73de04614604cbed62cc3b3198a639eb7b7d19cc2dfe3e787fe435f1464beee42a9a3cab6f1d3bfe66cd00317220ee770fd779b24c12c7698e251e875"
|
||||
|
||||
keyPair, err := NewRSAKeyPair(exponent, "", modulus)
|
||||
if err != nil {
|
||||
t.Fatalf("创建 RSA 密钥对失败: %v", err)
|
||||
}
|
||||
|
||||
// 测试加密
|
||||
plaintext := "241703070163"
|
||||
ciphertext, err := keyPair.Encrypt(plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("加密失败: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("明文: %s", plaintext)
|
||||
t.Logf("密文(十六进制): %s", ciphertext)
|
||||
|
||||
// 验证密文不为空
|
||||
if len(ciphertext) == 0 {
|
||||
t.Error("密文为空")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSAKeyPair_EncryptBytes(t *testing.T) {
|
||||
exponent := "10001"
|
||||
modulus := "a1155dc73de04614604cbed62cc3b3198a639eb7b7d19cc2dfe3e787fe435f1464beee42a9a3cab6f1d3bfe66cd00317220ee770fd779b24c12c7698e251e875"
|
||||
|
||||
keyPair, err := NewRSAKeyPair(exponent, "", modulus)
|
||||
if err != nil {
|
||||
t.Fatalf("创建 RSA 密钥对失败: %v", err)
|
||||
}
|
||||
|
||||
// 测试字节加密
|
||||
plaintext := []byte("Test data 测试数据")
|
||||
ciphertext, err := keyPair.EncryptBytes(plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("字节加密失败: %v", err)
|
||||
}
|
||||
|
||||
if len(ciphertext) == 0 {
|
||||
t.Error("密文字节为空")
|
||||
}
|
||||
|
||||
t.Logf("密文长度: %d bytes", len(ciphertext))
|
||||
}
|
||||
|
||||
func TestNewRSAKeyPairFromPublicKey(t *testing.T) {
|
||||
// 使用你提供的 X.509 格式的 base64 公钥
|
||||
publicKey := "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCpbRy8ZoyQvRPpUDIXycglTwVDcYrLcv7P9HI4E7/TAPZiI3GN0ckTUVVWRvoo/re3uCOOJK+F+Ufh19XiRIdoPYCULsSyEjcLLiyAS09mVOzhAQco84E/lM24T7rRTVID7LIgWPewyN8Nd5vzHET4Q3qTthoL8n82BwC1iXuVQIDAQAB"
|
||||
|
||||
keyPair, err := NewRSAKeyPairFromPublicKey(publicKey)
|
||||
if err != nil {
|
||||
t.Fatalf("从公钥创建 RSA 密钥对失败: %v", err)
|
||||
}
|
||||
|
||||
// 测试加密
|
||||
plaintext := "241703070163"
|
||||
ciphertext, err := keyPair.Encrypt(plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("加密失败: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("明文: %s", plaintext)
|
||||
t.Logf("密文(十六进制): %s", ciphertext)
|
||||
|
||||
// 验证密文不为空
|
||||
if len(ciphertext) == 0 {
|
||||
t.Error("密文为空")
|
||||
}
|
||||
|
||||
// 验证密文长度(1024位密钥应该产生128字节 = 256个十六进制字符)
|
||||
expectedHexLen := (keyPair.GetModulus().BitLen() + 7) / 8 * 2
|
||||
if len(ciphertext) != expectedHexLen {
|
||||
t.Errorf("密文长度不正确: 期望 %d, 实际 %d", expectedHexLen, len(ciphertext))
|
||||
}
|
||||
|
||||
t.Logf("密钥长度: %d bits", keyPair.GetModulus().BitLen())
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"likedream-goframe/internal/application/cpdaily/types"
|
||||
"likedream-goframe/internal/application/filestorage"
|
||||
"likedream-goframe/internal/common/db"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/db"
|
||||
"likedream-goframe/internal/ent"
|
||||
"likedream-goframe/internal/ent/cpimage"
|
||||
"likedream-goframe/internal/utils"
|
||||
@@ -75,7 +75,7 @@ func UploadImageFromStorage(ctx context.Context, host string, imageId uuid.UUID,
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "上传图片失败: "+utils.ReadString(resp.Body)), oops.WithContext(ctx).With("response", utils.ReadString(resp.Body)).Wrap)
|
||||
}
|
||||
|
||||
previewUrl := GetPreviewUrlAsync(ctx, host, targetImage.Key, taskType, client)
|
||||
previewUrl := GetPreviewUrlAsync(ctx, host, imageUploadPolicy.FileName, taskType, client)
|
||||
|
||||
return previewUrl
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ package cpdaily
|
||||
|
||||
import (
|
||||
"context"
|
||||
"likedream-goframe/internal/common/cst"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"likedream-goframe/internal/cst"
|
||||
)
|
||||
|
||||
type Info struct {
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"likedream-goframe/internal/application/cookiejar"
|
||||
"likedream-goframe/internal/application/cpdaily/cryptoutil"
|
||||
"likedream-goframe/internal/application/cpdaily/school"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/samber/oops"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
// 南阳理工学院登录实现 RSA加密 & 验证码必出
|
||||
type CpdailyAhjzuLogin struct {
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func (c CpdailyAhjzuLogin) Login(ctx context.Context, schoolDetail *school.SchoolDetailInfo, username, password string) {
|
||||
jar := cookiejar.NewPersistCookieJar()
|
||||
|
||||
c.client.SetCookieJar(jar).SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
|
||||
// 获取合法的登录URL
|
||||
loginUrl := getCasValidLoginUrl(schoolDetail)
|
||||
|
||||
// 请求登录页面并获取真正的登录URL
|
||||
loginPageResponse := getAhjzuLoginPageResponse(ctx, c.client, loginUrl)
|
||||
|
||||
defer loginPageResponse.Body.Close()
|
||||
loginPageBytes, err := io.ReadAll(loginPageResponse.Body)
|
||||
utils.OkOrPanic(err)
|
||||
loginResponseUrl := loginPageResponse.RawResponse.Request.URL
|
||||
|
||||
// 解析登录页面
|
||||
loginPage, err := goquery.NewDocumentFromReader(bytes.NewReader(loginPageBytes))
|
||||
utils.OkOrPanic(err)
|
||||
// 判断登录类型
|
||||
var (
|
||||
allInputs = map[string]string{}
|
||||
fm1Node = loginPage.Find("#credentials").First()
|
||||
)
|
||||
inputs := fm1Node.Find("input")
|
||||
|
||||
for _, input := range inputs.EachIter() {
|
||||
name, exists := input.Attr("name")
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
value, _ := input.Attr("value")
|
||||
allInputs[name] = value
|
||||
}
|
||||
|
||||
allInputs["username"] = username
|
||||
|
||||
// 处理密码
|
||||
allInputs["password"] = cryptoutil.MD5(password)
|
||||
|
||||
// 解决常规图片验证码
|
||||
captchaUrl := getAhjzuCaptchaUrl(loginResponseUrl)
|
||||
captchaResult := ResolveOcrCaptcha(ctx, captchaUrl, c.client)
|
||||
|
||||
allInputs["validateCode"] = captchaResult
|
||||
|
||||
// 登录提交
|
||||
loginPostResponse, err := c.client.R().
|
||||
SetFormData(allInputs).
|
||||
WithContext(ctx).
|
||||
Post(loginResponseUrl.String())
|
||||
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
WithContext(ctx).
|
||||
With("loginResponseUrl", loginResponseUrl).
|
||||
With("allInputs", allInputs).
|
||||
Wrap(err)
|
||||
})
|
||||
|
||||
defer loginPostResponse.Body.Close()
|
||||
|
||||
switch loginPostResponse.StatusCode() {
|
||||
case 302:
|
||||
handleCasLoginSuccess(ctx, loginPostResponse, schoolDetail, username, password, c.client)
|
||||
case 200:
|
||||
fallthrough
|
||||
case 401:
|
||||
doc, err := goquery.NewDocumentFromReader(loginPostResponse.Body)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
WithContext(ctx).
|
||||
With("loginResponseUrl", loginResponseUrl).
|
||||
With("allInputs", allInputs).
|
||||
Wrap(err)
|
||||
})
|
||||
|
||||
if isCasNeedKickOut(doc) {
|
||||
kickoutResponse := submitCasKickOut(ctx, loginResponseUrl.String(), doc, c.client)
|
||||
handleCasLoginSuccess(ctx, kickoutResponse, schoolDetail, username, password, c.client)
|
||||
return
|
||||
}
|
||||
|
||||
message := getCasErrorMessage(doc)
|
||||
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, fmt.Sprintf("今日校园登录失败: %s", message)), func(err error) error {
|
||||
return oops.
|
||||
WithContext(ctx).
|
||||
With("loginResponseUrl", loginResponseUrl).
|
||||
With("allInputs", allInputs).
|
||||
With("errorMessage", message).
|
||||
Wrap(errors.NewBusinessError(503, message))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getAhjzuLoginPageResponse(ctx context.Context, client *resty.Client, loginUrl string) *resty.Response {
|
||||
res1, err := client.R().Get(loginUrl)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
With("loginUrl", loginUrl).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
if res1.StatusCode() != http.StatusFound && res1.StatusCode() != http.StatusMovedPermanently {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "今日校园登录失败: 登录页面获取失败"), func(err error) error {
|
||||
return oops.
|
||||
With("loginUrl", loginUrl).
|
||||
With("statusCode", res1.StatusCode()).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
}
|
||||
location := res1.RawResponse.Header.Get("Location")
|
||||
res2, err := client.R().Get(location)
|
||||
if res2.StatusCode() != http.StatusFound && res2.StatusCode() != http.StatusMovedPermanently {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "今日校园登录失败: 登录页面获取失败"), func(err error) error {
|
||||
return oops.
|
||||
With("loginUrl", loginUrl).
|
||||
With("statusCode", res2.StatusCode()).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
}
|
||||
location = res2.RawResponse.Header.Get("Location")
|
||||
res3, err := client.R().Get(location)
|
||||
if res3.StatusCode() != http.StatusFound && res3.StatusCode() != http.StatusMovedPermanently {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "今日校园登录失败: 登录页面获取失败"), func(err error) error {
|
||||
return oops.
|
||||
With("loginUrl", loginUrl).
|
||||
With("statusCode", res3.StatusCode()).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
}
|
||||
location = res3.RawResponse.Header.Get("Location")
|
||||
res4, err := client.R().Get(location)
|
||||
if res4.StatusCode() != http.StatusFound && res3.StatusCode() != http.StatusMovedPermanently {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "今日校园登录失败: 登录页面获取失败"), func(err error) error {
|
||||
return oops.
|
||||
With("loginUrl", loginUrl).
|
||||
With("statusCode", res4.StatusCode()).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
}
|
||||
location = res4.RawResponse.Header.Get("Location")
|
||||
res5, err := client.R().Get(location)
|
||||
if err != nil {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "今日校园登录失败: 登录页面获取失败"), func(err error) error {
|
||||
return oops.
|
||||
With("loginUrl", loginUrl).
|
||||
With("statusCode", res4.StatusCode()).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
}
|
||||
return res5
|
||||
}
|
||||
func getAhjzuCaptchaUrl(url *url.URL) string {
|
||||
return fmt.Sprintf("%s://%s/sso/code/code.jsp", url.Scheme, url.Host)
|
||||
}
|
||||
|
||||
func (c CpdailyAhjzuLogin) GetCookieJar() http.CookieJar {
|
||||
return c.client.CookieJar()
|
||||
}
|
||||
|
||||
func (c CpdailyAhjzuLogin) GetCookieString() string {
|
||||
cookieJar := c.GetCookieJar()
|
||||
serializableJar := cookieJar.(*cookiejar.PersistCookieJar)
|
||||
return serializableJar.Serialize()
|
||||
}
|
||||
|
||||
func (c CpdailyAhjzuLogin) GetClient() *resty.Client {
|
||||
return c.client
|
||||
}
|
||||
|
||||
func NewAhjzu(ctx context.Context) *CpdailyAhjzuLogin {
|
||||
return &CpdailyAhjzuLogin{
|
||||
client: httpclient.NewCpdailyClient(),
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,21 @@ import (
|
||||
|
||||
func GetCpdailyLoginBySchoolInfo(ctx context.Context, info *school.SchoolDetailInfo) CpdailyLogin {
|
||||
switch info.Id {
|
||||
// 南昌理工学院
|
||||
case "d6e8f2ca-10b2-4599-bdb8-4b33d419658d":
|
||||
return NewNut(ctx)
|
||||
// 安徽建筑大学
|
||||
case "ahjzu":
|
||||
return NewAhjzu(ctx)
|
||||
// 南宁师范大学
|
||||
case "2ba0ea59-a764-4482-bb2b-c924b726b04c":
|
||||
return NewNnnu(ctx)
|
||||
// 西安体育学院
|
||||
case "ddf0639d-394d-48a2-a166-07ed84b7e6f0":
|
||||
fallthrough
|
||||
// 河北石油职业技术大学
|
||||
case "01e270d1-00e2-4ce9-b0af-5d5fbd462b8a":
|
||||
return NewIap(ctx)
|
||||
default:
|
||||
if info.IsIap() {
|
||||
return NewIap(ctx)
|
||||
|
||||
@@ -9,18 +9,21 @@ import (
|
||||
"likedream-goframe/internal/application/cookiejar"
|
||||
"likedream-goframe/internal/application/cpdaily/school"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
"likedream-goframe/internal/common/cst"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/cst"
|
||||
"likedream-goframe/internal/framework/redis"
|
||||
"likedream-goframe/internal/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/avast/retry-go/v4"
|
||||
"github.com/deatil/go-cryptobin/cryptobin/crypto"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/samber/oops"
|
||||
"github.com/tidwall/gjson"
|
||||
"resty.dev/v3"
|
||||
@@ -37,7 +40,7 @@ type CpdailyCasLogin struct {
|
||||
|
||||
func NewCas(ctx context.Context) CpdailyLogin {
|
||||
return &CpdailyCasLogin{
|
||||
client: httpclient.Cpdaily.Clone(ctx),
|
||||
client: httpclient.NewCpdailyClient(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +59,7 @@ func (c *CpdailyCasLogin) GetClient() *resty.Client {
|
||||
func (c *CpdailyCasLogin) Login(ctx context.Context, schoolDetail *school.SchoolDetailInfo, username, password string) {
|
||||
jar := cookiejar.NewPersistCookieJar()
|
||||
|
||||
c.client = c.client.SetCookieJar(jar).SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
c.client.SetCookieJar(jar).SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
|
||||
// 获取合法的登录URL
|
||||
loginUrl := getCasValidLoginUrl(schoolDetail)
|
||||
@@ -76,7 +79,7 @@ func (c *CpdailyCasLogin) Login(ctx context.Context, schoolDetail *school.School
|
||||
loginResponseUrl := loginPageResponse.RawResponse.Request.URL
|
||||
|
||||
// 关闭client的重定向
|
||||
c.client = c.client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
c.client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
|
||||
// 解析登录页面
|
||||
loginPage, err := goquery.NewDocumentFromReader(bytes.NewReader(loginPageBytes))
|
||||
@@ -155,11 +158,11 @@ func (c *CpdailyCasLogin) Login(ctx context.Context, schoolDetail *school.School
|
||||
allInputs[name] = value
|
||||
}
|
||||
} else {
|
||||
utils.OkOrPanic(oops.With("schoolDetail", schoolDetail).
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "页面中未找到登录表单"), oops.With("schoolDetail", schoolDetail).
|
||||
With("username", username).
|
||||
With("password", password).
|
||||
WithContext(ctx).
|
||||
Wrap(errors.NewBusinessError(503, "页面中未找到登录表单")))
|
||||
Wrap)
|
||||
}
|
||||
|
||||
// 修改部分参数
|
||||
@@ -189,19 +192,37 @@ func (c *CpdailyCasLogin) Login(ctx context.Context, schoolDetail *school.School
|
||||
allInputs["password"] = encryptCasPassword(password, salt)
|
||||
}
|
||||
|
||||
// 声明需要滑块验证码的学校id
|
||||
var SliderCaptchaIds = g.Cfg().MustGet(ctx, cst.ConfigSliderSchoolIds).Strings()
|
||||
// []string{"1018764144934965", "sdhyxy", "hqdx"}
|
||||
// 判断是否需要验证码
|
||||
if isCasNeedCaptcha(ctx, loginResponseUrl, loginType, username, c.client) {
|
||||
// 解决验证码
|
||||
captchaUrl := getCasCaptchaUrl(loginResponseUrl, loginType)
|
||||
// 如果是滑块验证码
|
||||
if slices.Contains(SliderCaptchaIds, schoolDetail.Id) {
|
||||
e := retry.Do(func() error {
|
||||
return utils.Catch(func() {
|
||||
captchaUrl := getCasSliderCaptchaUrl(loginResponseUrl)
|
||||
verifyUrl := getCasSliderVerifyUrl(loginResponseUrl)
|
||||
ResolveCasSliderCaptcha(ctx, captchaUrl, verifyUrl, c.client)
|
||||
})
|
||||
}, retry.MaxDelay(15*time.Second), retry.Attempts(5), retry.Context(ctx))
|
||||
if e != nil {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "滑块验证码解析失败").WithInnerError(e), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
} else {
|
||||
// 解决常规图片验证码
|
||||
captchaUrl := getCasCaptchaUrl(loginResponseUrl, loginType)
|
||||
|
||||
captchaResult := ResolveOcrCaptcha(ctx, captchaUrl, c.client)
|
||||
captchaResult := ResolveOcrCaptcha(ctx, captchaUrl, c.client)
|
||||
|
||||
switch loginType {
|
||||
case 0:
|
||||
allInputs["captchaResponse"] = captchaResult
|
||||
default:
|
||||
allInputs["captcha"] = captchaResult
|
||||
switch loginType {
|
||||
case 0:
|
||||
allInputs["captchaResponse"] = captchaResult
|
||||
default:
|
||||
allInputs["captcha"] = captchaResult
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
loginPostResponse, err := c.client.R().
|
||||
@@ -270,7 +291,7 @@ func (c *CpdailyCasLogin) ConfirmAdd(ctx context.Context, sc *school.SchoolDetai
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
// 提交确认添加
|
||||
client := httpclient.Cpdaily.Clone(ctx)
|
||||
client := httpclient.NewCpdailyClient()
|
||||
client.SetCookieJar(cookieJar)
|
||||
post, err := client.R().SetFormData(map[string]string{
|
||||
"service": service,
|
||||
@@ -334,7 +355,7 @@ func handleCasLoginSuccess(ctx context.Context, response *resty.Response, school
|
||||
jumpUrl, err := response.RawResponse.Location()
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
client = client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
|
||||
verifyResponse, err := client.R().
|
||||
WithContext(ctx).
|
||||
@@ -370,6 +391,10 @@ func handleCasLoginSuccess(ctx context.Context, response *resty.Response, school
|
||||
utils.OkOrPanic(errors.NewBusinessError(201, "多因子验证码认证").WithData(key))
|
||||
// 否则应该是登录成功
|
||||
} else if statusCode == 200 || statusCode == 404 {
|
||||
all, _ := io.ReadAll(verifyResponse.Body)
|
||||
if strings.Contains(string(all), "完善资料") || strings.Contains(string(all), "为了你的帐号安全") {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "请手动学工号登录完善信息,修改密码").WithData(schoolInfo.WecloudUrl))
|
||||
}
|
||||
ActivateSession(ctx, schoolInfo.WecloudUrl, client)
|
||||
} else {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "未知错误导致今日校园登录失败"),
|
||||
@@ -399,7 +424,7 @@ func submitCasKickOut(ctx context.Context, requestUrl string, doc *goquery.Docum
|
||||
submitForm[name] = value
|
||||
}
|
||||
|
||||
client = client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
|
||||
response, err := client.R().
|
||||
SetFormData(submitForm).
|
||||
@@ -431,11 +456,16 @@ func submitCasKickOut(ctx context.Context, requestUrl string, doc *goquery.Docum
|
||||
}
|
||||
|
||||
func getCasValidLoginUrl(info *school.SchoolDetailInfo) string {
|
||||
if strings.Contains(info.AmpUrl, "campusphere") {
|
||||
// 特殊学校单独处理
|
||||
// 甘肃农业大学
|
||||
if info.Id == "gsau" {
|
||||
return info.IdsUrl
|
||||
}
|
||||
if strings.Contains(info.AmpUrl, "campusphere") || strings.Contains(info.AmpUrl, "wec-portal-mobile") {
|
||||
return info.AmpUrl
|
||||
} else if strings.Contains(info.AmpUrl2, "campusphere") {
|
||||
} else if strings.Contains(info.AmpUrl2, "campusphere") || strings.Contains(info.AmpUrl2, "wec-portal-mobile") {
|
||||
return info.AmpUrl2
|
||||
} else if strings.Contains(info.AmpUrl3, "campusphere") {
|
||||
} else if strings.Contains(info.AmpUrl3, "campusphere") || strings.Contains(info.AmpUrl3, "wec-portal-mobile") {
|
||||
return info.AmpUrl3
|
||||
}
|
||||
|
||||
@@ -529,3 +559,12 @@ func getCasCaptchaUrl(url *url.URL, loginType int) string {
|
||||
return fmt.Sprintf("%s://%s/authserver/getCaptcha.htl", url.Scheme, url.Host)
|
||||
}
|
||||
}
|
||||
|
||||
func getCasSliderCaptchaUrl(url *url.URL) string {
|
||||
// /authserver/common/openSliderCaptcha.htl?_=1762873725346
|
||||
return fmt.Sprintf("%s://%s/authserver/common/openSliderCaptcha.htl", url.Scheme, url.Host)
|
||||
}
|
||||
|
||||
func getCasSliderVerifyUrl(url *url.URL) string {
|
||||
return fmt.Sprintf("%s://%s/authserver/common/verifySliderCaptcha.htl", url.Scheme, url.Host)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"likedream-goframe/internal/application/cpdaily/cryptoutil"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
type CpdailyCodeLogin struct {
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewCode(ctx context.Context) *CpdailyCodeLogin {
|
||||
return &CpdailyCodeLogin{
|
||||
client: httpclient.NewCpdailyClient(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CpdailyCodeLogin) GetLoginCode(mobile string) {
|
||||
mobileBytes := cryptoutil.AesEncrypt([]byte(mobile))
|
||||
mobile = base64.StdEncoding.EncodeToString(mobileBytes)
|
||||
c.client.R().SetBody(map[string]string{
|
||||
"a": mobile,
|
||||
"b": "first_v4",
|
||||
}).Post("https://mobile.campushoy.com/app/auth/authentication/mobile/messageCode/v-8222")
|
||||
}
|
||||
|
||||
func (c *CpdailyCodeLogin) LoginByCode(mobile, code string) {
|
||||
type loginStruct struct {
|
||||
C string `json:"c"`
|
||||
D string `json:"d"`
|
||||
E string `json:"e"`
|
||||
F string `json:"f"`
|
||||
}
|
||||
data := loginStruct{
|
||||
C: mobile,
|
||||
D: code,
|
||||
E: "CPDAILY",
|
||||
F: "86",
|
||||
}
|
||||
marshal, _ := sonic.Marshal(data)
|
||||
marshalBytes := cryptoutil.AesEncrypt(marshal)
|
||||
c.client.R().SetBody(map[string]string{
|
||||
"a": base64.StdEncoding.EncodeToString(marshalBytes),
|
||||
"b": "first_v4",
|
||||
}).Post("https://mobile.campushoy.com/app/auth/authentication/mobileLogin/v-8222")
|
||||
}
|
||||
@@ -14,7 +14,12 @@ import (
|
||||
)
|
||||
|
||||
func ActivateSession(ctx context.Context, host string, client *resty.Client) {
|
||||
client = client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
// 处理cookie,干掉path
|
||||
jar := client.CookieJar()
|
||||
realJar := jar.(*cookiejar.PersistCookieJar)
|
||||
realJar.RemoveCookieInfo()
|
||||
client.SetCookieJar(realJar)
|
||||
client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
|
||||
host = strings.TrimSuffix(host, "/")
|
||||
|
||||
@@ -27,7 +32,7 @@ func ActivateSession(ctx context.Context, host string, client *resty.Client) {
|
||||
|
||||
defer sessionResponse.Body.Close()
|
||||
|
||||
if !sessionResponse.IsSuccess() {
|
||||
if !sessionResponse.IsSuccess() && sessionResponse.StatusCode() != 502 {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "Session激活失败"),
|
||||
func(err error) error {
|
||||
return oops.
|
||||
@@ -78,34 +83,26 @@ func ActivateSession(ctx context.Context, host string, client *resty.Client) {
|
||||
activateStep1()
|
||||
activateStep2()
|
||||
|
||||
client = client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
}
|
||||
|
||||
func CookieEnabled(ctx context.Context, host string, client *resty.Client) bool {
|
||||
client = client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
defer func() {
|
||||
client = client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
}()
|
||||
client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
host = strings.TrimSuffix(host, "/")
|
||||
sessionResponse, err := client.R().
|
||||
WithContext(ctx).
|
||||
SetBody(map[string]any{}).
|
||||
Post(fmt.Sprintf("%s/wec-counselor-attendance-apps/student/attendance/getStuSignInfosByWeekMonth", host))
|
||||
Post(fmt.Sprintf("%s/wec-counselor-attendance-apps/student/attendance/getStuAttendacesInOneDay", host))
|
||||
|
||||
utils.OkOrPanic(err)
|
||||
defer sessionResponse.Body.Close()
|
||||
|
||||
if !sessionResponse.IsSuccess() {
|
||||
return sessionResponse.StatusCode() == 405
|
||||
}
|
||||
responseContentType := sessionResponse.Header().Get("Content-Type")
|
||||
return strings.Contains(responseContentType, "json")
|
||||
return sessionResponse.StatusCode() != 302
|
||||
}
|
||||
|
||||
func GetClient(ctx context.Context, cookie string) *resty.Client {
|
||||
cookieJar := cookiejar.NewPersistCookieJar()
|
||||
cookieJar.Deserialize(cookie)
|
||||
client := httpclient.Cpdaily.Clone(ctx)
|
||||
// 不使用 Clone,创建全新的独立 client 实例避免状态共享
|
||||
client := httpclient.NewCpdailyClient()
|
||||
client.SetCookieJar(cookieJar)
|
||||
return client
|
||||
}
|
||||
|
||||
@@ -5,25 +5,29 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"likedream-goframe/internal/application/cookiejar"
|
||||
"likedream-goframe/internal/application/cpdaily/cryptoutil"
|
||||
"likedream-goframe/internal/application/cpdaily/school"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/oops"
|
||||
"github.com/tidwall/gjson"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
const iapRsaPubKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCpbRy8ZoyQvRPpUDIXycglTwVDcYrLcv7P9HI4E7/TAPZiI3GN0ckTUVVWRvoo/re3uCOOJK+F+Ufh19XiRIdoPYCULsSyEjcLLiyAS09mVOzhAQco84E/lM24T7rRTVID7LIgWPewyN8Nd5vzHET4Q3qTthoL8n82BwC1iXuVQIDAQAB"
|
||||
|
||||
type CpdailyIapLogin struct {
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewIap(ctx context.Context) *CpdailyIapLogin {
|
||||
return &CpdailyIapLogin{
|
||||
client: httpclient.Cpdaily.Clone(ctx),
|
||||
client: httpclient.NewCpdailyClient(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,14 +38,13 @@ func (c *CpdailyIapLogin) Login(ctx context.Context, schoolDetail *school.School
|
||||
loginUrl := getIapValidLoginUrl(schoolDetail)
|
||||
|
||||
jar := cookiejar.NewPersistCookieJar()
|
||||
c.client = c.client.SetCookieJar(jar).
|
||||
c.client.SetCookieJar(jar).
|
||||
SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
|
||||
ltInfoResponse, err := c.client.R().
|
||||
WithContext(ctx).
|
||||
SetBody(map[string]any{}).
|
||||
Post(fmt.Sprintf("%s/iap/security/lt", loginUrl))
|
||||
|
||||
// 获取基础lt
|
||||
preLoginUrl := loginUrl + "/portal/login"
|
||||
c.client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
loginRedirectResponse, err := c.client.R().Get(preLoginUrl)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
In("CpdailyIapLogin.Login").
|
||||
@@ -50,6 +53,25 @@ func (c *CpdailyIapLogin) Login(ctx context.Context, schoolDetail *school.School
|
||||
With("password", password).
|
||||
Wrap(err)
|
||||
})
|
||||
u := loginRedirectResponse.RawResponse.Request.URL
|
||||
oldLt := strings.Split(u.String(), "=")[1]
|
||||
c.client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
ltInfoResponse, err := c.client.R().
|
||||
WithContext(ctx).
|
||||
SetFormData(map[string]string{
|
||||
"lt": oldLt,
|
||||
}).
|
||||
Post(fmt.Sprintf("%s/iap/security/lt", loginUrl))
|
||||
rsa, _ := cryptoutil.NewRSAKeyPairFromPublicKey(iapRsaPubKey)
|
||||
password, _ = rsa.Encrypt(password)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
In("CpdailyIapLogin.Login").
|
||||
With("schoolDetail", schoolDetail).
|
||||
With("username", username).
|
||||
With("password", "{rsa}"+password).
|
||||
Wrap(err)
|
||||
})
|
||||
|
||||
defer ltInfoResponse.Body.Close()
|
||||
|
||||
@@ -64,8 +86,9 @@ func (c *CpdailyIapLogin) Login(ctx context.Context, schoolDetail *school.School
|
||||
"rememberMe": "true",
|
||||
"dllt": "",
|
||||
"mobile": "",
|
||||
"captcha": "",
|
||||
"username": username,
|
||||
"password": password,
|
||||
"password": "{rsa}" + password,
|
||||
}
|
||||
|
||||
if isIapNeedCaptcha(ctx, loginUrl, username, c.client) {
|
||||
@@ -103,7 +126,7 @@ func (c *CpdailyIapLogin) Login(ctx context.Context, schoolDetail *school.School
|
||||
Wrap(err)
|
||||
})
|
||||
|
||||
c.client = c.client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
c.client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
|
||||
finalLoginResponse, err := c.client.R().
|
||||
WithContext(ctx).
|
||||
@@ -123,7 +146,7 @@ func (c *CpdailyIapLogin) Login(ctx context.Context, schoolDetail *school.School
|
||||
|
||||
ActivateSession(ctx, schoolDetail.WecloudUrl, c.client)
|
||||
|
||||
c.client = c.client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
c.client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
default:
|
||||
loginResponseBytes, err := io.ReadAll(loginResponse.Body)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
@@ -142,9 +165,18 @@ func (c *CpdailyIapLogin) Login(ctx context.Context, schoolDetail *school.School
|
||||
case "CAPTCHA_NOTMATCH":
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "Iap登录失败, 验证码识别失败"))
|
||||
case "FAIL_UPNOTMATCH":
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "Iap登录失败, 用户名或密码错误"))
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "Iap登录失败, 用户名或密码错误"), func(err error) error {
|
||||
return oops.With("username", username).
|
||||
With("password", password).
|
||||
With("userId", utils.GetUserIdNotPanic(ctx)).
|
||||
In("Cpdaily.Login").New("Iap登录失败, 用户名或密码错误")
|
||||
})
|
||||
default:
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, fmt.Sprintf("Iap登录失败, 状态识别码: %s", resultCode)))
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, fmt.Sprintf("Iap登录失败, 状态识别码: %s", resultCode)),
|
||||
oops.With("username", username).
|
||||
With("password", password).
|
||||
With("userId", utils.GetUserIdNotPanic(ctx)).
|
||||
In("Cpdaily.Login").Wrap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"likedream-goframe/internal/application/cookiejar"
|
||||
"likedream-goframe/internal/application/cpdaily/cryptoutil"
|
||||
"likedream-goframe/internal/application/cpdaily/school"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/samber/oops"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
// 南宁师范大学登录实现
|
||||
type CpdailyNnnuLogin struct {
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func (c CpdailyNnnuLogin) Login(ctx context.Context, schoolDetail *school.SchoolDetailInfo, username, password string) {
|
||||
jar := cookiejar.NewPersistCookieJar()
|
||||
|
||||
c.client.SetCookieJar(jar).SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
|
||||
// 获取合法的登录URL
|
||||
loginUrl := getCasValidLoginUrl(schoolDetail)
|
||||
|
||||
// 请求登录页面并获取真正的登录URL
|
||||
loginPageResponse, err := c.client.R().
|
||||
WithContext(ctx).
|
||||
Get(loginUrl)
|
||||
|
||||
defer loginPageResponse.Body.Close()
|
||||
loginPageBytes, err := io.ReadAll(loginPageResponse.Body)
|
||||
utils.OkOrPanic(err)
|
||||
loginResponseUrl := loginPageResponse.RawResponse.Request.URL
|
||||
// 关闭client的重定向
|
||||
c.client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
// 解析登录页面
|
||||
loginPage, err := goquery.NewDocumentFromReader(bytes.NewReader(loginPageBytes))
|
||||
utils.OkOrPanic(err)
|
||||
// 判断登录类型
|
||||
var (
|
||||
allInputs = map[string]string{}
|
||||
form = loginPage.Find("form").First()
|
||||
)
|
||||
inputs := form.Find("input")
|
||||
|
||||
for _, input := range inputs.EachIter() {
|
||||
name, exists := input.Attr("name")
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
value, _ := input.Attr("value")
|
||||
allInputs[name] = value
|
||||
}
|
||||
|
||||
allInputs["username"] = username
|
||||
|
||||
// 处理密码
|
||||
allInputs["password"] = cryptoutil.MD5(password)
|
||||
|
||||
// 解决常规图片验证码
|
||||
captchaUrl := getAhjzuCaptchaUrl(loginResponseUrl)
|
||||
captchaResult := ResolveOcrCaptcha(ctx, captchaUrl, c.client)
|
||||
|
||||
allInputs["validateCode"] = captchaResult
|
||||
|
||||
// 登录提交
|
||||
loginPostResponse, err := c.client.R().
|
||||
SetFormData(allInputs).
|
||||
WithContext(ctx).
|
||||
Post(loginResponseUrl.String())
|
||||
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
WithContext(ctx).
|
||||
With("loginResponseUrl", loginResponseUrl).
|
||||
With("allInputs", allInputs).
|
||||
Wrap(err)
|
||||
})
|
||||
|
||||
defer loginPostResponse.Body.Close()
|
||||
|
||||
switch loginPostResponse.StatusCode() {
|
||||
case 302:
|
||||
handleCasLoginSuccess(ctx, loginPostResponse, schoolDetail, username, password, c.client)
|
||||
case 200:
|
||||
fallthrough
|
||||
case 401:
|
||||
doc, err := goquery.NewDocumentFromReader(loginPostResponse.Body)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
WithContext(ctx).
|
||||
With("loginResponseUrl", loginResponseUrl).
|
||||
With("allInputs", allInputs).
|
||||
Wrap(err)
|
||||
})
|
||||
|
||||
if isCasNeedKickOut(doc) {
|
||||
kickoutResponse := submitCasKickOut(ctx, loginResponseUrl.String(), doc, c.client)
|
||||
handleCasLoginSuccess(ctx, kickoutResponse, schoolDetail, username, password, c.client)
|
||||
return
|
||||
}
|
||||
|
||||
message := getCasErrorMessage(doc)
|
||||
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, fmt.Sprintf("今日校园登录失败: %s", message)), func(err error) error {
|
||||
return oops.
|
||||
WithContext(ctx).
|
||||
With("loginResponseUrl", loginResponseUrl).
|
||||
With("allInputs", allInputs).
|
||||
With("errorMessage", message).
|
||||
Wrap(errors.NewBusinessError(503, message))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getNuuuCaptchaUrl(url *url.URL) string {
|
||||
return fmt.Sprintf("%s://%s/sso/code/code.jsp", url.Scheme, url.Host)
|
||||
}
|
||||
|
||||
func (c CpdailyNnnuLogin) GetCookieJar() http.CookieJar {
|
||||
return c.client.CookieJar()
|
||||
}
|
||||
|
||||
func (c CpdailyNnnuLogin) GetCookieString() string {
|
||||
cookieJar := c.GetCookieJar()
|
||||
serializableJar := cookieJar.(*cookiejar.PersistCookieJar)
|
||||
return serializableJar.Serialize()
|
||||
}
|
||||
|
||||
func (c CpdailyNnnuLogin) GetClient() *resty.Client {
|
||||
return c.client
|
||||
}
|
||||
|
||||
func NewNnnu(ctx context.Context) CpdailyLogin {
|
||||
return &CpdailyNnnuLogin{
|
||||
client: httpclient.NewCpdailyClient(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"likedream-goframe/internal/application/cookiejar"
|
||||
"likedream-goframe/internal/application/cpdaily/cryptoutil"
|
||||
"likedream-goframe/internal/application/cpdaily/school"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/samber/oops"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
// 南阳理工学院登录实现 RSA加密 & 验证码必出
|
||||
type CpdailyNutLogin struct {
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func (c CpdailyNutLogin) Login(ctx context.Context, schoolDetail *school.SchoolDetailInfo, username, password string) {
|
||||
jar := cookiejar.NewPersistCookieJar()
|
||||
|
||||
c.client.SetCookieJar(jar).SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
|
||||
// 获取合法的登录URL
|
||||
loginUrl := getCasValidLoginUrl(schoolDetail)
|
||||
|
||||
// 请求登录页面并获取真正的登录URL
|
||||
loginPageResponse, err := c.client.R().
|
||||
WithContext(ctx).
|
||||
Get(loginUrl)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
defer loginPageResponse.Body.Close()
|
||||
loginPageBytes, err := io.ReadAll(loginPageResponse.Body)
|
||||
utils.OkOrPanic(err)
|
||||
loginResponseUrl := loginPageResponse.RawResponse.Request.URL
|
||||
// 关闭client的重定向
|
||||
c.client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
|
||||
// 解析登录页面
|
||||
loginPage, err := goquery.NewDocumentFromReader(bytes.NewReader(loginPageBytes))
|
||||
utils.OkOrPanic(err)
|
||||
// 判断登录类型
|
||||
var (
|
||||
allInputs = map[string]string{}
|
||||
fm1Node = loginPage.Find("#fm1").First()
|
||||
)
|
||||
inputs := fm1Node.Find("input")
|
||||
|
||||
for _, input := range inputs.EachIter() {
|
||||
name, exists := input.Attr("name")
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
value, _ := input.Attr("value")
|
||||
allInputs[name] = value
|
||||
}
|
||||
|
||||
allInputs["username"] = username
|
||||
|
||||
// 处理密码
|
||||
|
||||
type NutPubKeyResponse struct {
|
||||
Modulus string `json:"modulus"`
|
||||
Exponent string `json:"exponent"`
|
||||
}
|
||||
var pubKey NutPubKeyResponse
|
||||
pubKeyResponse, err := c.client.R().Get(getNutPubKey(loginResponseUrl))
|
||||
utils.OkOrPanic(err)
|
||||
defer pubKeyResponse.Body.Close()
|
||||
pubKeyBytes, err := io.ReadAll(pubKeyResponse.Body)
|
||||
utils.OkOrPanic(err)
|
||||
sonic.Unmarshal(pubKeyBytes, &pubKey)
|
||||
rsa := cryptoutil.NewNutRSAKeyPair(pubKey.Exponent, "", pubKey.Modulus)
|
||||
password = rsa.EncryptedString(reverseStringASCII(password))
|
||||
allInputs["password"] = password
|
||||
if isNutNeedCaptcha(ctx, loginResponseUrl, c.client) {
|
||||
// 解决常规图片验证码
|
||||
captchaUrl := getNutCaptchaUrl(loginResponseUrl)
|
||||
|
||||
captchaResult := ResolveOcrCaptcha(ctx, captchaUrl, c.client)
|
||||
|
||||
allInputs["authcode"] = captchaResult
|
||||
} else {
|
||||
allInputs["authcode"] = ""
|
||||
}
|
||||
|
||||
// 登录提交
|
||||
loginPostResponse, err := c.client.R().
|
||||
SetFormData(allInputs).
|
||||
WithContext(ctx).
|
||||
Post(loginResponseUrl.String())
|
||||
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
WithContext(ctx).
|
||||
With("loginResponseUrl", loginResponseUrl).
|
||||
With("allInputs", allInputs).
|
||||
Wrap(err)
|
||||
})
|
||||
|
||||
defer loginPostResponse.Body.Close()
|
||||
|
||||
switch loginPostResponse.StatusCode() {
|
||||
case 302:
|
||||
handleCasLoginSuccess(ctx, loginPostResponse, schoolDetail, username, password, c.client)
|
||||
case 200:
|
||||
fallthrough
|
||||
case 401:
|
||||
doc, err := goquery.NewDocumentFromReader(loginPostResponse.Body)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
WithContext(ctx).
|
||||
With("loginResponseUrl", loginResponseUrl).
|
||||
With("allInputs", allInputs).
|
||||
Wrap(err)
|
||||
})
|
||||
|
||||
if isCasNeedKickOut(doc) {
|
||||
kickoutResponse := submitCasKickOut(ctx, loginResponseUrl.String(), doc, c.client)
|
||||
handleCasLoginSuccess(ctx, kickoutResponse, schoolDetail, username, password, c.client)
|
||||
return
|
||||
}
|
||||
|
||||
message := getCasErrorMessage(doc)
|
||||
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, fmt.Sprintf("今日校园登录失败: %s", message)), func(err error) error {
|
||||
return oops.
|
||||
WithContext(ctx).
|
||||
With("loginResponseUrl", loginResponseUrl).
|
||||
With("allInputs", allInputs).
|
||||
With("errorMessage", message).
|
||||
Wrap(errors.NewBusinessError(503, message))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
func getNutCaptchaUrl(url *url.URL) string {
|
||||
return fmt.Sprintf("%s://%s/cas/kaptcha", url.Scheme, url.Host)
|
||||
}
|
||||
func isNutNeedCaptcha(ctx context.Context, url *url.URL, client *resty.Client) bool {
|
||||
|
||||
requestURL := getNutNeedCaptchaUrl(url)
|
||||
|
||||
captchaResponse, err := client.R().
|
||||
WithContext(ctx).
|
||||
Get(requestURL)
|
||||
|
||||
utils.OkOrPanic(oops.With().
|
||||
With("requestURL", requestURL).
|
||||
WithContext(ctx).
|
||||
Wrap(err))
|
||||
|
||||
defer captchaResponse.Body.Close()
|
||||
|
||||
if captchaResponse.IsError() {
|
||||
utils.OkOrPanic(oops.With().
|
||||
With("requestURL", requestURL).
|
||||
WithContext(ctx).
|
||||
Wrap(errors.NewBusinessError(503, "请求验证码失败")))
|
||||
}
|
||||
|
||||
responseBytes, err := io.ReadAll(captchaResponse.Body)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
return strings.ToLower(strings.TrimSpace(string(responseBytes))) != "false"
|
||||
}
|
||||
|
||||
func getNutPubKey(url *url.URL) string {
|
||||
return fmt.Sprintf("%s://%s/cas/v2/getPubKey", url.Scheme, url.Host)
|
||||
}
|
||||
func getNutNeedCaptchaUrl(url *url.URL) string {
|
||||
return fmt.Sprintf("%s://%s/cas/v2/getKaptchaStatus", url.Scheme, url.Host)
|
||||
}
|
||||
|
||||
func (c CpdailyNutLogin) GetCookieJar() http.CookieJar {
|
||||
return c.client.CookieJar()
|
||||
}
|
||||
|
||||
func (c CpdailyNutLogin) GetCookieString() string {
|
||||
cookieJar := c.GetCookieJar()
|
||||
serializableJar := cookieJar.(*cookiejar.PersistCookieJar)
|
||||
return serializableJar.Serialize()
|
||||
}
|
||||
|
||||
func (c CpdailyNutLogin) GetClient() *resty.Client {
|
||||
return c.client
|
||||
}
|
||||
|
||||
func NewNut(ctx context.Context) *CpdailyNutLogin {
|
||||
return &CpdailyNutLogin{
|
||||
client: httpclient.NewCpdailyClient(),
|
||||
}
|
||||
}
|
||||
func reverseStringASCII(s string) string {
|
||||
bytes := []byte(s)
|
||||
for i, j := 0, len(bytes)-1; i < j; i, j = i+1, j-1 {
|
||||
bytes[i], bytes[j] = bytes[j], bytes[i]
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
@@ -3,70 +3,35 @@ package login
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
"likedream-goframe/internal/cst"
|
||||
"likedream-goframe/internal/application/captcha"
|
||||
"likedream-goframe/internal/utils"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/samber/oops"
|
||||
"github.com/tidwall/gjson"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
func ResolveOcrCaptcha(ctx context.Context, url string, client *resty.Client) string {
|
||||
captchaResponse, err := client.R().
|
||||
byteData := DownCode2Byte(ctx, url, client)
|
||||
return captcha.ApiAutoOCRVerification(byteData)
|
||||
}
|
||||
|
||||
func DownCode2Byte(ctx context.Context, url string, client *resty.Client) []byte {
|
||||
res, err := client.R().
|
||||
WithContext(ctx).
|
||||
Get(url)
|
||||
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
With("captchaUrl", url).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
|
||||
defer func() {
|
||||
_ = captchaResponse.Body.Close()
|
||||
}()
|
||||
|
||||
ocrUrl := g.Cfg().MustGetWithEnv(ctx, cst.ConfigOcrServerUrl).String()
|
||||
|
||||
ocrResponse, err := httpclient.Common.R().
|
||||
SetMultipartFields(&resty.MultipartField{
|
||||
Name: "file",
|
||||
FileName: "captcha.jpg",
|
||||
ContentType: captchaResponse.Header().Get("Content-Type"),
|
||||
Reader: captchaResponse.Body,
|
||||
}).
|
||||
WithContext(ctx).
|
||||
Post(ocrUrl + "/ocr")
|
||||
|
||||
defer res.Body.Close()
|
||||
all, err := io.ReadAll(res.Body)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
With("captchaUrl", url).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
|
||||
defer func() {
|
||||
_ = ocrResponse.Body.Close()
|
||||
}()
|
||||
|
||||
responseBytes, err := io.ReadAll(ocrResponse.Body)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
responseJSON := gjson.ParseBytes(responseBytes)
|
||||
|
||||
data := responseJSON.Get("data").String()
|
||||
|
||||
if data == "" {
|
||||
utils.OkOrPanic(oops.
|
||||
In("ResolveOcrCaptcha").
|
||||
With("captchaUrl", url).
|
||||
With("message", responseJSON.Get("message").String()).
|
||||
WithContext(ctx).
|
||||
New("OCR服务返回数据为空"))
|
||||
}
|
||||
|
||||
return data
|
||||
return all
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
"likedream-goframe/internal/common/cst"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/utils"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/samber/oops"
|
||||
"github.com/tidwall/gjson"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
type CasSliderCaptchaReponse struct {
|
||||
BigImage string `json:"bigImage"`
|
||||
SmallImage string `json:"smallImage"`
|
||||
TagWidth int `json:"tagWidth"`
|
||||
YHeight int `json:"yHeight"`
|
||||
}
|
||||
|
||||
type CasSliderResult struct {
|
||||
ErrorCode int `json:"errorCode"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
}
|
||||
|
||||
func ResolveCasSliderCaptcha(ctx context.Context, captchaUrl string, verifyUrl string, client *resty.Client) {
|
||||
res, err := client.R().
|
||||
WithContext(ctx).
|
||||
Get(captchaUrl)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
With("captchaUrl", captchaUrl).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
defer res.Body.Close()
|
||||
bytes, _ := io.ReadAll(res.Body)
|
||||
var captcha CasSliderCaptchaReponse
|
||||
sonic.Unmarshal(bytes, &captcha)
|
||||
//width := slider.VerifySliderWidthByBase64(captcha.BigImage, captcha.SmallImage)
|
||||
width := ApiVerifySliderWidthByBase64(captcha.BigImage, captcha.SmallImage)
|
||||
res2, err2 := client.R().
|
||||
WithContext(ctx).
|
||||
SetFormData(map[string]string{
|
||||
"canvasLength": "280",
|
||||
"moveLength": width,
|
||||
}).
|
||||
Post(verifyUrl)
|
||||
utils.OkOrPanic(err2, func(err error) error {
|
||||
return oops.
|
||||
With("verifyUrl", verifyUrl).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
defer res2.Body.Close()
|
||||
bytes, _ = io.ReadAll(res2.Body)
|
||||
str := string(bytes)
|
||||
if len(str) > 0 {
|
||||
var result CasSliderResult
|
||||
sonic.Unmarshal(bytes, &result)
|
||||
if result.ErrorCode != 1 {
|
||||
utils.OkOrPanic(oops.WithContext(ctx).Wrap(errors.NewBusinessError(500, "滑块解析错误")))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func ApiVerifySliderWidthByBase64(bigImage string, smallImage string) string {
|
||||
ctx := gctx.GetInitCtx()
|
||||
baseUrl := g.Config().MustGetWithEnv(ctx, cst.ConfigCaptchaBaseUrl).String() + "/slider"
|
||||
client := httpclient.Cpdaily.Clone(ctx)
|
||||
client.SetDoNotParseResponse(false)
|
||||
post, err := client.R().SetBody(map[string]any{
|
||||
"big_image": bigImage,
|
||||
"small_image": smallImage,
|
||||
}).Post(baseUrl)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
With("AutoOCRVerification", baseUrl).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
result := post.String()
|
||||
return gjson.Get(result, "data").String()
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package map_info
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/utils"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/samber/oops"
|
||||
)
|
||||
|
||||
func SearchAddressLongitudeAndLatitude(ctx context.Context, address string) []MapPoi {
|
||||
client := httpclient.Cpdaily.Clone(ctx).SetDoNotParseResponse(false)
|
||||
// 首先访问获取ak
|
||||
response, err := client.R().Get("https://lbsyun.baidu.com/jsdemo/demo/webgl0_5.htm")
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.WithContext(ctx).With("url", "https://lbsyun.baidu.com/jsdemo/demo/webgl0_5.htm").In("SearchAddressLongitudeAndLatitude.GetAk").Wrap(err)
|
||||
})
|
||||
ak := ParseAkString(response.String())
|
||||
timestamp := fmt.Sprintf("%d", time.Now().UnixMilli())
|
||||
mapResponse, err := client.R().
|
||||
SetQueryParams(map[string]string{
|
||||
"qt": "placesug",
|
||||
"query": address,
|
||||
"callback": "BMapGL._rd._cbk25241",
|
||||
"region": "131",
|
||||
"city_limit": "false",
|
||||
"t": timestamp,
|
||||
"from": "jsapi",
|
||||
"ak": ak,
|
||||
"v": "gl",
|
||||
"timeStamp": timestamp,
|
||||
}).
|
||||
SetHeaders(map[string]string{
|
||||
"Accept": "*/*",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
"Connection": "keep-alive",
|
||||
"Referer": "https://lbsyun.baidu.com/",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
|
||||
}).
|
||||
Get("https://api.map.baidu.com/")
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.WithContext(ctx).With("address", address).In("SearchAddressLongitudeAndLatitude.QueryMap").Wrap(err)
|
||||
})
|
||||
|
||||
// 从JSONP响应中提取JSON数据
|
||||
str := ParseJsonpToJson(mapResponse.String())
|
||||
var jsonData MapJSONData
|
||||
sonic.Unmarshal([]byte(str), &jsonData)
|
||||
return jsonData.Result.PoiList
|
||||
}
|
||||
|
||||
// 正则提取ak // <script type="text/javascript" src="//api.map.baidu.com/api?type=webgl&v=1.0&ak=E4805d16520de693a3fe707cdc962045"></script>
|
||||
func ParseAkString(data string) string {
|
||||
reg := regexp.MustCompile(`ak=([^"]+)`)
|
||||
matches := reg.FindStringSubmatch(data)
|
||||
if len(matches) < 2 {
|
||||
utils.OkOrPanic(errors.NewBusinessError(500, "获取ak出错"))
|
||||
}
|
||||
return matches[1]
|
||||
}
|
||||
|
||||
// 从JSONP响应中提取JSON数据
|
||||
// 输入: /**/BMapGL._rd._cbk25241 && BMapGL._rd._cbk25241({...})
|
||||
// 输出: {...}
|
||||
func ParseJsonpToJson(jsonpData string) string {
|
||||
// 匹配 callback(json) 格式,提取括号中的内容
|
||||
reg := regexp.MustCompile(`\((\{.*\})\)`)
|
||||
matches := reg.FindStringSubmatch(jsonpData)
|
||||
if len(matches) < 2 {
|
||||
utils.OkOrPanic(errors.NewBusinessError(500, "解析JSONP数据出错"))
|
||||
}
|
||||
return matches[1]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package map_info
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
)
|
||||
|
||||
func TestMapSearch(t *testing.T) {
|
||||
res := SearchAddressLongitudeAndLatitude(gctx.New(), "河北石油职业技术大学c座")
|
||||
marshal, _ := json.Marshal(res)
|
||||
t.Log(string(marshal))
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package map_info
|
||||
|
||||
type MapJSONData struct {
|
||||
Status int `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Result MapResult `json:"result"`
|
||||
}
|
||||
type MapLocation struct {
|
||||
Lat float64 `json:"lat"`
|
||||
Lng float64 `json:"lng"`
|
||||
}
|
||||
type MapPoi struct {
|
||||
Name string `json:"name"`
|
||||
Location MapLocation `json:"location"`
|
||||
UID string `json:"uid"`
|
||||
Province string `json:"province"`
|
||||
City string `json:"city"`
|
||||
District string `json:"district"`
|
||||
Business string `json:"business"`
|
||||
Cityid string `json:"cityid"`
|
||||
Tag string `json:"tag"`
|
||||
Address string `json:"address"`
|
||||
Children []interface{} `json:"children"`
|
||||
Adcode string `json:"adcode"`
|
||||
}
|
||||
type MapResult struct {
|
||||
Q string `json:"q"`
|
||||
PoiList []MapPoi `json:"poi_list"`
|
||||
}
|
||||
@@ -1,22 +1,27 @@
|
||||
package school
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
cryptoutil2 "likedream-goframe/internal/application/cpdaily/cryptoutil"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
cst2 "likedream-goframe/internal/common/cst"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/framework/redis"
|
||||
"likedream-goframe/internal/utils"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/samber/oops"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
var lock = sync.RWMutex{}
|
||||
|
||||
func GetSchoolList(ctx context.Context) []CpdailySchoolBriefInfo {
|
||||
response, err := httpclient.Cpdaily.R().
|
||||
response, err := httpclient.Cpdaily.Clone(ctx).R().
|
||||
WithContext(ctx).
|
||||
Get("https://static.campushoy.com/apicache/tenantListSort")
|
||||
|
||||
@@ -52,7 +57,17 @@ func GetSchoolList(ctx context.Context) []CpdailySchoolBriefInfo {
|
||||
}
|
||||
|
||||
func GetSchoolDetail(ctx context.Context, tenantId string) SchoolDetailInfo {
|
||||
response, err := httpclient.Cpdaily.R().Clone(ctx).
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
client := redis.GetClient()
|
||||
redisResult := client.Get(ctx, cst2.RedisSchoolInfo+tenantId)
|
||||
if redisResult.Err() == nil {
|
||||
var ret SchoolDetailInfo
|
||||
err := sonic.Unmarshal([]byte(redisResult.Val()), &ret)
|
||||
utils.OkOrPanic(err)
|
||||
return parseSpecialSchool(ret)
|
||||
}
|
||||
response, err := httpclient.Cpdaily.Clone(ctx).R().
|
||||
SetHeaders(map[string]string{
|
||||
"deviceType": "2",
|
||||
"CpdailyClientType": "CPDAILY",
|
||||
@@ -89,6 +104,14 @@ func GetSchoolDetail(ctx context.Context, tenantId string) SchoolDetailInfo {
|
||||
WithContext(ctx).
|
||||
Wrap(errors.NewBusinessError(503, "学校ID不存在").Decorate("empty school detail")))
|
||||
}
|
||||
|
||||
return ret[0]
|
||||
client.Set(ctx, cst2.RedisSchoolInfo+tenantId, ret[0], time.Duration(cst2.RedisSchoolInfoExpire)*time.Second)
|
||||
return parseSpecialSchool(ret[0])
|
||||
}
|
||||
|
||||
func parseSpecialSchool(s SchoolDetailInfo) SchoolDetailInfo {
|
||||
// 甘肃农业大学
|
||||
if s.Id == "gsau" {
|
||||
s.WecloudUrl = "https://fdm.gsau.edu.cn"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
package submitcaptcha
|
||||
|
||||
//
|
||||
//import (
|
||||
// "context"
|
||||
// "io"
|
||||
// "likedream-goframe/internal/application/cpdaily/types"
|
||||
// "likedream-goframe/internal/application/recognizer/classifier"
|
||||
// "likedream-goframe/internal/common/errors"
|
||||
// "likedream-goframe/internal/utils"
|
||||
// "strings"
|
||||
//
|
||||
// "github.com/bytedance/sonic"
|
||||
// "resty.dev/v3"
|
||||
//)
|
||||
//
|
||||
//type AIResolver struct {
|
||||
// client *resty.Client
|
||||
// host string
|
||||
// deviceId string
|
||||
// taskType types.TaskType
|
||||
//}
|
||||
//
|
||||
//func NewAIResolver(client *resty.Client, host, deviceId string, taskType types.TaskType) SubmitCaptchaResolver {
|
||||
// return &AIResolver{
|
||||
// client: client,
|
||||
// host: host,
|
||||
// deviceId: deviceId,
|
||||
// taskType: taskType,
|
||||
// }
|
||||
//}
|
||||
//func (r *AIResolver) GetValidationInfo(ctx context.Context) ValidationInfo {
|
||||
// // GetValidationInfo 获取验证信息
|
||||
// reqData := map[string]string{
|
||||
// "deviceId": r.deviceId,
|
||||
// }
|
||||
//
|
||||
// resp, err := r.client.R().
|
||||
// SetBody(reqData).
|
||||
// Post(r.getValidateUrl())
|
||||
// utils.OkOrPanic(err)
|
||||
//
|
||||
// apiResp := utils.ReadJson[ApiResponse](resp.Body)
|
||||
//
|
||||
// if apiResp.Datas == nil {
|
||||
// message := "获取提交验证码信息失败"
|
||||
// if apiResp.Message != "" {
|
||||
// message = apiResp.Message
|
||||
// }
|
||||
// utils.OkOrPanic(errors.NewBusinessError(503, message))
|
||||
// }
|
||||
//
|
||||
// var validationInfo ValidationInfo
|
||||
// err = sonic.Unmarshal(apiResp.Datas, &validationInfo)
|
||||
// utils.OkOrPanic(err)
|
||||
//
|
||||
// return validationInfo
|
||||
//
|
||||
//}
|
||||
//func (r *AIResolver) GetCaptchaTicket(ctx context.Context, info ValidationInfo) string {
|
||||
// captchaInfo := r.createCaptcha(info)
|
||||
// codes := r.getResult(captchaInfo)
|
||||
// return r.submitCaptcha(info, captchaInfo, codes)
|
||||
//}
|
||||
//
|
||||
//func (r *AIResolver) getResult(data CpSubmitCaptchaForAI) []string {
|
||||
// name := data.Name
|
||||
// infos := data.ImageInfos
|
||||
// var result []string
|
||||
// for _, item := range infos {
|
||||
// url := item.Path
|
||||
// get, err := r.client.R().Get(url)
|
||||
// utils.OkOrPanic(err)
|
||||
// defer get.Body.Close()
|
||||
// bytes, _ := io.ReadAll(get.Body)
|
||||
// //todo 将图片保存方便后面训练
|
||||
// //os.WriteFile(item.Code+".png", bytes, 0666)
|
||||
// if classifier.ClassifyImage(bytes, name) {
|
||||
// result = append(result, item.Code)
|
||||
// }
|
||||
// }
|
||||
// return result
|
||||
//}
|
||||
//
|
||||
//// getValidateUrl 根据任务类型获取验证URL
|
||||
//func (r *AIResolver) getValidateUrl() string {
|
||||
// baseUrl := strings.TrimSuffix(r.host, "/")
|
||||
//
|
||||
// switch r.taskType {
|
||||
// case types.TaskTypeCollection:
|
||||
// return baseUrl + "/wec-counselor-collector-apps/stu/collector/checkValidation"
|
||||
// case types.TaskTypeSign:
|
||||
// return baseUrl + "/wec-counselor-sign-apps/stu/sign/checkValidation"
|
||||
// case types.TaskTypeSleepCheck:
|
||||
// return baseUrl + "/wec-counselor-attendance-apps/student/attendance/checkValidation"
|
||||
// default:
|
||||
// utils.OkOrPanic(errors.NewBusinessError(400, "不支持的任务类型"))
|
||||
// return ""
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// createCaptcha 创建验证码
|
||||
//func (r *AIResolver) createCaptcha(info ValidationInfo) CpSubmitCaptchaForAI {
|
||||
// baseUrl := strings.TrimSuffix(r.host, "/")
|
||||
// url := baseUrl + "/captcha-open-api/v1/captcha/create/scenesImage"
|
||||
//
|
||||
// resp, err := r.client.R().
|
||||
// SetHeader("deviceId", r.deviceId).
|
||||
// SetMultipartFormData(map[string]string{
|
||||
// "accountKey": info.AccountKey,
|
||||
// "sceneCode": info.SceneCode,
|
||||
// "tenantId": info.TenantId,
|
||||
// "userId": info.UserId,
|
||||
// }).
|
||||
// Post(url)
|
||||
// utils.OkOrPanic(err)
|
||||
//
|
||||
// apiResp := utils.ReadJson[ApiResponse](resp.Body)
|
||||
//
|
||||
// if apiResp.Result == nil {
|
||||
// message := "获取提交验证码失败"
|
||||
// if apiResp.Message != "" {
|
||||
// message = apiResp.Message
|
||||
// }
|
||||
// utils.OkOrPanic(errors.NewBusinessError(503, message))
|
||||
// }
|
||||
//
|
||||
// var result CpSubmitCaptchaForAI
|
||||
// err = sonic.Unmarshal(apiResp.Result, &result)
|
||||
// utils.OkOrPanic(err)
|
||||
// return result
|
||||
//}
|
||||
//
|
||||
//// submitCaptcha 提交验证码
|
||||
//func (r *AIResolver) submitCaptcha(info ValidationInfo, submitCaptcha CpSubmitCaptchaForAI, codes []string) string {
|
||||
// baseUrl := strings.TrimSuffix(r.host, "/")
|
||||
// url := baseUrl + "/captcha-open-api/v1/captcha/validate/scenesImage"
|
||||
//
|
||||
// formData := map[string]string{
|
||||
// "accountKey": info.AccountKey,
|
||||
// "sceneCode": info.SceneCode,
|
||||
// "tenantId": info.TenantId,
|
||||
// "userId": info.UserId,
|
||||
// "scenesImageCode": submitCaptcha.Code,
|
||||
// }
|
||||
//
|
||||
// // 创建multipart form request
|
||||
// req := r.client.R().SetHeader("deviceId", r.deviceId)
|
||||
//
|
||||
// // 添加基本表单数据
|
||||
// for key, value := range formData {
|
||||
// req = req.SetMultipartFormData(map[string]string{key: value})
|
||||
// }
|
||||
//
|
||||
// // 添加验证码列表
|
||||
// for _, code := range codes {
|
||||
// req = req.SetMultipartField("scenesImageCodes", "", "", strings.NewReader(code))
|
||||
// }
|
||||
//
|
||||
// resp, err := req.Post(url)
|
||||
// utils.OkOrPanic(err)
|
||||
//
|
||||
// apiResp := utils.ReadJson[ApiResponse](resp.Body)
|
||||
//
|
||||
// if apiResp.Code != "200" {
|
||||
// message := "提交验证码失败"
|
||||
// if apiResp.Message != "" {
|
||||
// message = apiResp.Message
|
||||
// }
|
||||
// utils.OkOrPanic(errors.NewBusinessError(503, message))
|
||||
// }
|
||||
//
|
||||
// if apiResp.Result == nil {
|
||||
// utils.OkOrPanic(errors.NewBusinessError(503, "今日校园返回空验证码结果"))
|
||||
// }
|
||||
//
|
||||
// var result string
|
||||
// err = sonic.Unmarshal(apiResp.Result, &result)
|
||||
// utils.OkOrPanic(err)
|
||||
//
|
||||
// return result
|
||||
//}
|
||||
@@ -20,6 +20,15 @@ type CpSubmitCaptcha struct {
|
||||
RawResponseJson json.RawMessage `json:"-"`
|
||||
}
|
||||
|
||||
type CpSubmitCaptchaForAI struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
ImageInfos []struct {
|
||||
Code string `json:"code"`
|
||||
Path string `json:"path"`
|
||||
} `json:"imageInfos"`
|
||||
}
|
||||
|
||||
// ApiResponse 通用API响应结构体
|
||||
type ApiResponse struct {
|
||||
Code json.Number `json:"code"`
|
||||
@@ -33,9 +42,7 @@ type ApiResponse struct {
|
||||
type ITStarResponse struct {
|
||||
Code json.Number `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
SuccCode []string `json:"succCode"`
|
||||
} `json:"data"`
|
||||
Data []string `json:"data"`
|
||||
}
|
||||
|
||||
type SubmitCaptchaResolver interface {
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"likedream-goframe/internal/application/cpdaily/types"
|
||||
"likedream-goframe/internal/common/cst"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/cst"
|
||||
"likedream-goframe/internal/utils"
|
||||
"strings"
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
type RuoliSubmitCaptchaResolver struct {
|
||||
type NormalSubmitCaptchaResolver struct {
|
||||
client *resty.Client
|
||||
host string
|
||||
deviceId string
|
||||
@@ -24,12 +24,12 @@ type RuoliSubmitCaptchaResolver struct {
|
||||
itstarUrl string
|
||||
}
|
||||
|
||||
var _ SubmitCaptchaResolver = (*RuoliSubmitCaptchaResolver)(nil)
|
||||
var _ SubmitCaptchaResolver = (*NormalSubmitCaptchaResolver)(nil)
|
||||
|
||||
// NewRuoliResolver 创建若李验证码解析器
|
||||
func NewRuoliResolver(client *resty.Client, host, deviceId string, taskType types.TaskType) *RuoliSubmitCaptchaResolver {
|
||||
baseUrl := g.Config().MustGetWithEnv(gctx.GetInitCtx(), cst.ConfigRuoliCaptchaBaseUrl).String()
|
||||
return &RuoliSubmitCaptchaResolver{
|
||||
// NewNormalResolver 创建若李验证码解析器
|
||||
func NewNormalResolver(client *resty.Client, host, deviceId string, taskType types.TaskType) *NormalSubmitCaptchaResolver {
|
||||
baseUrl := g.Config().MustGetWithEnv(gctx.GetInitCtx(), cst.ConfigCaptchaBaseUrl).String() + "/classifier"
|
||||
return &NormalSubmitCaptchaResolver{
|
||||
client: client,
|
||||
host: host,
|
||||
deviceId: deviceId,
|
||||
@@ -39,12 +39,12 @@ func NewRuoliResolver(client *resty.Client, host, deviceId string, taskType type
|
||||
}
|
||||
|
||||
// getITStarUrl 获取ITStar服务URL
|
||||
func (r *RuoliSubmitCaptchaResolver) getITStarUrl() string {
|
||||
func (r *NormalSubmitCaptchaResolver) getITStarUrl() string {
|
||||
if r.itstarUrl != "" {
|
||||
return r.itstarUrl
|
||||
}
|
||||
|
||||
r.itstarUrl = g.Config().MustGetWithEnv(gctx.GetInitCtx(), cst.ConfigRuoliCaptchaBaseUrl).String()
|
||||
r.itstarUrl = g.Config().MustGetWithEnv(gctx.GetInitCtx(), cst.ConfigCaptchaBaseUrl).String() + "/classifier"
|
||||
if r.itstarUrl == "" {
|
||||
utils.OkOrPanic(errors.NewBusinessError(500, "未配置ITStarUrl"))
|
||||
}
|
||||
@@ -53,7 +53,7 @@ func (r *RuoliSubmitCaptchaResolver) getITStarUrl() string {
|
||||
}
|
||||
|
||||
// getValidateUrl 根据任务类型获取验证URL
|
||||
func (r *RuoliSubmitCaptchaResolver) getValidateUrl() string {
|
||||
func (r *NormalSubmitCaptchaResolver) getValidateUrl() string {
|
||||
baseUrl := strings.TrimSuffix(r.host, "/")
|
||||
|
||||
switch r.taskType {
|
||||
@@ -70,7 +70,7 @@ func (r *RuoliSubmitCaptchaResolver) getValidateUrl() string {
|
||||
}
|
||||
|
||||
// GetValidationInfo 获取验证信息
|
||||
func (r *RuoliSubmitCaptchaResolver) GetValidationInfo(ctx context.Context) ValidationInfo {
|
||||
func (r *NormalSubmitCaptchaResolver) GetValidationInfo(ctx context.Context) ValidationInfo {
|
||||
reqData := map[string]string{
|
||||
"deviceId": r.deviceId,
|
||||
}
|
||||
@@ -98,14 +98,14 @@ func (r *RuoliSubmitCaptchaResolver) GetValidationInfo(ctx context.Context) Vali
|
||||
}
|
||||
|
||||
// GetCaptchaTicket 获取验证码票据
|
||||
func (r *RuoliSubmitCaptchaResolver) GetCaptchaTicket(ctx context.Context, info ValidationInfo) string {
|
||||
func (r *NormalSubmitCaptchaResolver) GetCaptchaTicket(ctx context.Context, info ValidationInfo) string {
|
||||
captchaInfo := r.createCaptcha(info)
|
||||
codes := r.getResultFromItStar(captchaInfo.RawResponseJson)
|
||||
return r.submitCaptcha(info, captchaInfo, codes)
|
||||
}
|
||||
|
||||
// createCaptcha 创建验证码
|
||||
func (r *RuoliSubmitCaptchaResolver) createCaptcha(info ValidationInfo) CpSubmitCaptcha {
|
||||
func (r *NormalSubmitCaptchaResolver) createCaptcha(info ValidationInfo) CpSubmitCaptcha {
|
||||
baseUrl := strings.TrimSuffix(r.host, "/")
|
||||
url := baseUrl + "/captcha-open-api/v1/captcha/create/scenesImage"
|
||||
|
||||
@@ -139,16 +139,13 @@ func (r *RuoliSubmitCaptchaResolver) createCaptcha(info ValidationInfo) CpSubmit
|
||||
}
|
||||
|
||||
// getResultFromItStar 从ITStar获取识别结果
|
||||
func (r *RuoliSubmitCaptchaResolver) getResultFromItStar(rawResponse json.RawMessage) []string {
|
||||
func (r *NormalSubmitCaptchaResolver) getResultFromItStar(rawResponse json.RawMessage) []string {
|
||||
// 解析原始响应为map以便添加authKey
|
||||
var responseMap map[string]any
|
||||
err := sonic.Unmarshal(rawResponse, &responseMap)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
responseMap["authKey"] = "ITStarYYDS"
|
||||
|
||||
itstarUrl := strings.TrimSuffix(r.getITStarUrl(), "/")
|
||||
url := itstarUrl + "/captcha/unLimitValidate"
|
||||
url := r.getITStarUrl()
|
||||
|
||||
resp, err := r.client.R().
|
||||
SetBody(responseMap).
|
||||
@@ -161,11 +158,11 @@ func (r *RuoliSubmitCaptchaResolver) getResultFromItStar(rawResponse json.RawMes
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, fmt.Sprintf("验证码识别出错: %s", itstarResp.Msg)))
|
||||
}
|
||||
|
||||
return itstarResp.Data.SuccCode
|
||||
return itstarResp.Data
|
||||
}
|
||||
|
||||
// submitCaptcha 提交验证码
|
||||
func (r *RuoliSubmitCaptchaResolver) submitCaptcha(info ValidationInfo, submitCaptcha CpSubmitCaptcha, codes []string) string {
|
||||
func (r *NormalSubmitCaptchaResolver) submitCaptcha(info ValidationInfo, submitCaptcha CpSubmitCaptcha, codes []string) string {
|
||||
baseUrl := strings.TrimSuffix(r.host, "/")
|
||||
url := baseUrl + "/captcha-open-api/v1/captcha/validate/scenesImage"
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
func TestRuoliSubmitCaptchaResolver_GetCaptchaTicket(t *testing.T) {
|
||||
loginClient, detail := testutil.LoginTestUser()
|
||||
|
||||
resolver := NewRuoliResolver(loginClient, detail.WecloudUrl, "abcdefg", types.TaskTypeSleepCheck)
|
||||
resolver := NewNormalResolver(loginClient, detail.WecloudUrl, "abcdefg", types.TaskTypeSleepCheck)
|
||||
|
||||
info := resolver.GetValidationInfo(context.Background())
|
||||
|
||||
|
||||
@@ -2,27 +2,22 @@ package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"likedream-goframe/internal/application/cookiejar"
|
||||
"likedream-goframe/internal/application/cpdaily/school"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
errors2 "likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/ent/cptask"
|
||||
"likedream-goframe/internal/utils"
|
||||
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
func GetCpdailyTaskService(ctx context.Context, Type cptask.TaskType, cookie string, schoolId string) CpdailyTask {
|
||||
detail := school.GetSchoolDetail(ctx, schoolId)
|
||||
cookieJar := cookiejar.NewPersistCookieJar()
|
||||
cookieJar.Deserialize(cookie)
|
||||
client := httpclient.Cpdaily.Clone(ctx)
|
||||
client.SetCookieJar(cookieJar)
|
||||
func GetCpdailyTaskService(ctx context.Context, Type cptask.TaskType, client *resty.Client, schoolDetail school.SchoolDetailInfo) CpdailyTask {
|
||||
switch Type {
|
||||
case cptask.TaskTypeCollection:
|
||||
return NewCollectionTask(client, detail.WecloudUrl)
|
||||
return NewCollectionTask(client, schoolDetail.WecloudUrl)
|
||||
case cptask.TaskTypeSign:
|
||||
return NewAutoSignTask(client, detail.WecloudUrl)
|
||||
return NewAutoSignTask(client, schoolDetail.WecloudUrl)
|
||||
case cptask.TaskTypeSleepCheck:
|
||||
return NewSleepCheckTask(client, detail.WecloudUrl)
|
||||
return NewSleepCheckTask(client, schoolDetail.WecloudUrl)
|
||||
default:
|
||||
utils.OkOrPanic(errors2.NewBusinessError(400, "不支持的任务类型"))
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ func (c *CpdailyCollectionTask) QueryFormByTitle(ctx context.Context, titleGlob
|
||||
}
|
||||
|
||||
if matchTask == nil {
|
||||
utils.OkOrPanic(errors.NewBusinessError(404, fmt.Sprintf("未找到表单信息: %s", titleGlob)), func(err error) error {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, fmt.Sprintf("未找到表单信息: %s", titleGlob)), func(err error) error {
|
||||
return oops.With("host", c.host).
|
||||
In("CpdailyCollectionTask.QueryFormByTitle").
|
||||
Wrap(err)
|
||||
@@ -196,7 +196,7 @@ func (c *CpdailyCollectionTask) QueryFormByTitle(ctx context.Context, titleGlob
|
||||
detailDatas := detailJSON["datas"].MapStrVar()
|
||||
|
||||
if len(detailDatas) == 0 {
|
||||
utils.OkOrPanic(errors.NewBusinessError(404, "未找到表单数据"), func(err error) error {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "未找到表单数据"), func(err error) error {
|
||||
return oops.With("host", c.host).
|
||||
In("CpdailyCollectionTask.QueryFormByTitle").
|
||||
With("titleGlob", titleGlob).
|
||||
@@ -230,7 +230,7 @@ func (c *CpdailyCollectionTask) QueryFormByTitle(ctx context.Context, titleGlob
|
||||
formDatas := formJSON["datas"].MapStrVar()
|
||||
|
||||
if len(formDatas) == 0 {
|
||||
utils.OkOrPanic(errors.NewBusinessError(404, "未找到表单数据"), func(err error) error {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "未找到表单数据"), func(err error) error {
|
||||
return oops.With("host", c.host).
|
||||
In("CpdailyCollectionTask.QueryFormByTitle").
|
||||
With("titleGlob", titleGlob).
|
||||
@@ -354,7 +354,7 @@ func (c *CpdailyCollectionTask) ParseFormJson(ctx context.Context, rawJson map[s
|
||||
return list
|
||||
}
|
||||
|
||||
func (c *CpdailyCollectionTask) FillCustomForm(ctx context.Context, form map[string]*gvar.Var, paramList []*formtype.CpTaskFormItem, deviceId, username string) map[string]*gvar.Var {
|
||||
func (c *CpdailyCollectionTask) FillCustomForm(ctx context.Context, form map[string]*gvar.Var, paramList []*formtype.CpTaskFormItem, deviceId, imgId string) map[string]*gvar.Var {
|
||||
taskInfo := form["taskInfo"].MapStrVar()
|
||||
rows := form["rows"].Array()
|
||||
filledForm := make([]map[string]*gvar.Var, 0)
|
||||
|
||||
@@ -15,4 +15,5 @@ type CpTaskFormItem struct {
|
||||
Sort uint `json:"sort,omitempty"`
|
||||
CacheKey string `json:"cacheKey,omitempty"`
|
||||
Show bool `json:"show,omitempty"`
|
||||
Tip string `json:"tip,omitempty"`
|
||||
}
|
||||
|
||||
@@ -8,4 +8,5 @@ const (
|
||||
ItemTypeMulti
|
||||
ItemTypeImage
|
||||
ItemTypeAddress
|
||||
ItemTypeLatAndLon
|
||||
)
|
||||
|
||||
@@ -12,6 +12,6 @@ type CpdailyTask interface {
|
||||
QueryFormByTitle(ctx context.Context, titleGlob string) map[string]*gvar.Var
|
||||
QueryFormByYearMonth(ctx context.Context, titleGlob string, year, month string) map[string]*gvar.Var
|
||||
ParseFormJson(ctx context.Context, rawJson map[string]*gvar.Var) []*formtype.CpTaskFormItem
|
||||
FillCustomForm(ctx context.Context, form map[string]*gvar.Var, paramList []*formtype.CpTaskFormItem, deviceId, username string) map[string]*gvar.Var
|
||||
FillCustomForm(ctx context.Context, form map[string]*gvar.Var, paramList []*formtype.CpTaskFormItem, deviceId, imgId string) map[string]*gvar.Var
|
||||
Submit(ctx context.Context, form map[string]*gvar.Var, username, deviceId string)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"likedream-goframe/internal/application/cpdaily/types"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/utils"
|
||||
"math/rand/v2"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -147,8 +146,8 @@ func (c *CpdailyAutoSignTask) QueryFormByTitle(ctx context.Context, titleGlob st
|
||||
|
||||
findMatchTitle := func(jsonArr []any) map[string]*gvar.Var {
|
||||
for _, rawJson := range jsonArr {
|
||||
item, ok := rawJson.(map[string]*gvar.Var)
|
||||
if !ok {
|
||||
item := gvar.New(rawJson).MapStrVar()
|
||||
if len(item) == 0 {
|
||||
continue
|
||||
}
|
||||
taskName, exist := item["taskName"]
|
||||
@@ -178,7 +177,7 @@ func (c *CpdailyAutoSignTask) QueryFormByTitle(ctx context.Context, titleGlob st
|
||||
if matchTask == nil {
|
||||
matchTask = findMatchTitle(signedTasks)
|
||||
if matchTask == nil {
|
||||
utils.OkOrPanic(errors.NewBusinessError(404, fmt.Sprintf("任务未找到: %s", titleGlob)), func(err error) error {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, fmt.Sprintf("任务未找到: %s", titleGlob)), func(err error) error {
|
||||
return oops.With("host", c.host).
|
||||
In("CpdailyAutoSignTask.QueryFormByTitle").
|
||||
Wrap(err)
|
||||
@@ -210,7 +209,7 @@ func (c *CpdailyAutoSignTask) QueryFormByTitle(ctx context.Context, titleGlob st
|
||||
formDatas := detailResponseJSON["datas"].MapStrVar()
|
||||
|
||||
if len(formDatas) == 0 {
|
||||
utils.OkOrPanic(errors.NewBusinessError(404, "任务数据为空"), func(err error) error {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "任务数据为空"), func(err error) error {
|
||||
return oops.With("host", c.host).
|
||||
In("CpdailyAutoSignTask.QueryFormByTitle").
|
||||
With("titleGlob", titleGlob).
|
||||
@@ -293,7 +292,7 @@ func (c *CpdailyAutoSignTask) QueryFormByYearMonth(ctx context.Context, titleGlo
|
||||
if matchTask == nil {
|
||||
matchTask = findMatchTitle(signedTasks)
|
||||
if matchTask == nil {
|
||||
utils.OkOrPanic(errors.NewBusinessError(404, fmt.Sprintf("任务未找到: %s", titleGlob)), func(err error) error {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, fmt.Sprintf("任务未找到: %s", titleGlob)), func(err error) error {
|
||||
return oops.
|
||||
With("host", c.host).
|
||||
With("year", year).
|
||||
@@ -332,7 +331,7 @@ func (c *CpdailyAutoSignTask) QueryFormByYearMonth(ctx context.Context, titleGlo
|
||||
formDatas := detailResponseJSON["datas"].MapStrVar()
|
||||
|
||||
if len(formDatas) == 0 {
|
||||
utils.OkOrPanic(errors.NewBusinessError(404, "任务数据为空"), func(err error) error {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "任务数据为空"), func(err error) error {
|
||||
return oops.
|
||||
With("host", c.host).
|
||||
With("year", year).
|
||||
@@ -360,42 +359,48 @@ func (c *CpdailyAutoSignTask) ParseFormJson(ctx context.Context, rawJson map[str
|
||||
var list []*formtype.CpTaskFormItem
|
||||
|
||||
// 经纬度
|
||||
//dto := &formtype.CpTaskFormItem{
|
||||
// Title: "经度",
|
||||
// ColName: "lon",
|
||||
// Type: formtype.ItemTypeInput,
|
||||
// Required: true,
|
||||
//}
|
||||
//list = append(list, dto)
|
||||
//
|
||||
//dto = &formtype.CpTaskFormItem{
|
||||
// Title: "纬度",
|
||||
// ColName: "lat",
|
||||
// Type: formtype.ItemTypeInput,
|
||||
// Required: true,
|
||||
//}
|
||||
//list = append(list, dto)
|
||||
dto := &formtype.CpTaskFormItem{
|
||||
Title: "经度",
|
||||
ColName: "lon",
|
||||
Type: formtype.ItemTypeInput,
|
||||
Required: true,
|
||||
}
|
||||
list = append(list, dto)
|
||||
|
||||
dto = &formtype.CpTaskFormItem{
|
||||
Title: "纬度",
|
||||
ColName: "lat",
|
||||
Type: formtype.ItemTypeInput,
|
||||
Title: "经纬度",
|
||||
ColName: "latAndLon",
|
||||
Type: formtype.ItemTypeLatAndLon,
|
||||
Required: true,
|
||||
Tip: "您可以通过地址搜索签到经纬度,如果觉得不够准确可以自行修改",
|
||||
}
|
||||
list = append(list, dto)
|
||||
|
||||
dto = &formtype.CpTaskFormItem{
|
||||
Title: "签到具体地址",
|
||||
ColName: "address",
|
||||
Type: formtype.ItemTypeAddress,
|
||||
Type: formtype.ItemTypeInput,
|
||||
Required: true,
|
||||
Tip: "这个就是指签到完毕后查看时最底部那个最详细的地址信息哦",
|
||||
}
|
||||
list = append(list, dto)
|
||||
|
||||
var current uint
|
||||
|
||||
if isPhoto := rows["isPhoto"]; isPhoto != nil && isPhoto.Int() == 1 {
|
||||
dto = &formtype.CpTaskFormItem{
|
||||
Title: "签到照片",
|
||||
ColName: "signImg",
|
||||
Type: formtype.ItemTypeImage,
|
||||
Required: true,
|
||||
Number: current,
|
||||
Tip: "您可以上传多张图片,会顺序轮换更新的哦~",
|
||||
}
|
||||
list = append(list, dto)
|
||||
current++
|
||||
}
|
||||
|
||||
// 是否范围外签到
|
||||
@@ -404,7 +409,6 @@ func (c *CpdailyAutoSignTask) ParseFormJson(ctx context.Context, rawJson map[str
|
||||
ColName: "isMalposition",
|
||||
Type: formtype.ItemTypeSingle,
|
||||
Required: true,
|
||||
Number: current,
|
||||
Options: []formtype.CpTaskFormItemOption{
|
||||
{
|
||||
Content: "是",
|
||||
@@ -416,9 +420,9 @@ func (c *CpdailyAutoSignTask) ParseFormJson(ctx context.Context, rawJson map[str
|
||||
IsOtherItems: false,
|
||||
},
|
||||
},
|
||||
Tip: "一般在学校就是选否,如果在外实习、请假之类的才选是",
|
||||
}
|
||||
list = append(list, dto)
|
||||
current++
|
||||
|
||||
// 范围外签到原因
|
||||
dto = &formtype.CpTaskFormItem{
|
||||
@@ -427,16 +431,15 @@ func (c *CpdailyAutoSignTask) ParseFormJson(ctx context.Context, rawJson map[str
|
||||
Type: formtype.ItemTypeInput,
|
||||
Required: true,
|
||||
LogicWid: "abnormalReason",
|
||||
Number: current,
|
||||
}
|
||||
list = append(list, dto)
|
||||
current++
|
||||
|
||||
current := uint(1)
|
||||
// 处理额外字段
|
||||
if isNeedExtra := rows["isNeedExtra"]; isNeedExtra != nil && isNeedExtra.Int() == 1 {
|
||||
extraField := rows["extraField"].Array()
|
||||
for i, field := range extraField {
|
||||
fieldObj := field.(map[string]*gvar.Var)
|
||||
for _, field := range extraField {
|
||||
fieldObj := gvar.New(field).MapStrVar()
|
||||
title := fieldObj["title"].String()
|
||||
isRequired := fieldObj["isRequired"].Bool()
|
||||
hasOtherItems := fieldObj["hasOtherItems"].Bool()
|
||||
@@ -445,16 +448,17 @@ func (c *CpdailyAutoSignTask) ParseFormJson(ctx context.Context, rawJson map[str
|
||||
Title: title,
|
||||
ColName: title,
|
||||
Type: formtype.ItemTypeSingle,
|
||||
Number: uint(i),
|
||||
Number: current,
|
||||
Required: isRequired,
|
||||
IsOther: hasOtherItems,
|
||||
}
|
||||
current++
|
||||
|
||||
// 处理选项
|
||||
extraFieldItems := fieldObj["extraFieldItems"].Array()
|
||||
var options []formtype.CpTaskFormItemOption
|
||||
for _, item := range extraFieldItems {
|
||||
itemObj := item.(map[string]*gvar.Var)
|
||||
itemObj := gvar.New(item).MapStrVar()
|
||||
option := formtype.CpTaskFormItemOption{
|
||||
Content: itemObj["content"].String(),
|
||||
IsOtherItems: itemObj["isOtherItems"].Bool(),
|
||||
@@ -473,7 +477,7 @@ func (c *CpdailyAutoSignTask) ParseFormJson(ctx context.Context, rawJson map[str
|
||||
return list
|
||||
}
|
||||
|
||||
func (c *CpdailyAutoSignTask) FillCustomForm(ctx context.Context, form map[string]*gvar.Var, paramList []*formtype.CpTaskFormItem, deviceId, username string) map[string]*gvar.Var {
|
||||
func (c *CpdailyAutoSignTask) FillCustomForm(ctx context.Context, form map[string]*gvar.Var, paramList []*formtype.CpTaskFormItem, deviceId, imgId string) map[string]*gvar.Var {
|
||||
var resultForm map[string]*gvar.Var
|
||||
|
||||
// 填充必要变量
|
||||
@@ -491,6 +495,11 @@ func (c *CpdailyAutoSignTask) FillCustomForm(ctx context.Context, form map[strin
|
||||
lon = v.Content
|
||||
case "address":
|
||||
address = v.Content
|
||||
case "latAndLon":
|
||||
{
|
||||
res := strings.Split(v.Content, ",")
|
||||
lat, lon = res[1], res[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,36 +523,20 @@ func (c *CpdailyAutoSignTask) FillCustomForm(ctx context.Context, form map[strin
|
||||
utils.OkOrPanic(errors.NewBusinessError(400, "签到需要图片,但是当前图片为空"), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
|
||||
imageUuidSeq := strings.SplitSeq(targetParam.Content, ",")
|
||||
|
||||
var imageUuids []string
|
||||
|
||||
for imageUuid := range imageUuidSeq {
|
||||
imageUuid = strings.TrimSpace(imageUuid)
|
||||
if imageUuid == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
imageUuids = append(imageUuids, imageUuid)
|
||||
}
|
||||
|
||||
if len(imageUuids) == 0 {
|
||||
if imgId == "" {
|
||||
utils.OkOrPanic(errors.NewBusinessError(400, "签到照片未上传"), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
|
||||
// 随机选择一张照片
|
||||
imageUuid := imageUuids[rand.IntN(len(imageUuids))]
|
||||
|
||||
imageId, e := uuid.Parse(imageUuid)
|
||||
imageId, e := uuid.Parse(imgId)
|
||||
utils.OkOrPanic(e, oops.WithContext(ctx).Wrap)
|
||||
|
||||
previewUrl := imageuploader.UploadImageFromStorage(ctx, c.host, imageId, types.TaskTypeSign, c.client)
|
||||
|
||||
resultForm["signImg"] = gvar.New(previewUrl)
|
||||
resultForm["signPhotoUrl"] = gvar.New(previewUrl)
|
||||
}
|
||||
|
||||
// 处理验证码
|
||||
submitCaptchaResolver := submitcaptcha.NewRuoliResolver(c.client, c.host, deviceId, types.TaskTypeSign)
|
||||
submitCaptchaResolver := submitcaptcha.NewNormalResolver(c.client, c.host, deviceId, types.TaskTypeSign)
|
||||
captchaInfo := submitCaptchaResolver.GetValidationInfo(ctx)
|
||||
|
||||
if captchaInfo.Validation {
|
||||
@@ -554,7 +547,9 @@ func (c *CpdailyAutoSignTask) FillCustomForm(ctx context.Context, form map[strin
|
||||
ticket = submitCaptchaResolver.GetCaptchaTicket(ctx, captchaInfo)
|
||||
})
|
||||
}, retry.MaxDelay(15*time.Second), retry.Attempts(5), retry.Context(ctx))
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "提交验证码识别失败").WithInnerError(e), oops.WithContext(ctx).Wrap)
|
||||
if e != nil {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "提交验证码识别失败").WithInnerError(e), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
|
||||
resultForm["ticket"] = gvar.New(ticket)
|
||||
} else {
|
||||
@@ -577,8 +572,9 @@ func (c *CpdailyAutoSignTask) FillCustomForm(ctx context.Context, form map[strin
|
||||
var extraFieldItemValues []map[string]*gvar.Var
|
||||
|
||||
for i, field := range extraField {
|
||||
current := uint(i + 1)
|
||||
// 检查数字是否在范围内
|
||||
found := slices.Contains(numbers, uint(i))
|
||||
found := slices.Contains(numbers, current)
|
||||
if !found {
|
||||
utils.OkOrPanic(errors.NewBusinessError(500, "非法的额外信息数据"), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
@@ -586,7 +582,7 @@ func (c *CpdailyAutoSignTask) FillCustomForm(ctx context.Context, form map[strin
|
||||
// 找到对应的参数
|
||||
var targetParam *formtype.CpTaskFormItem
|
||||
for _, v := range paramList {
|
||||
if v.Number == uint(i) {
|
||||
if v.Number == current {
|
||||
targetParam = v
|
||||
break
|
||||
}
|
||||
@@ -596,7 +592,7 @@ func (c *CpdailyAutoSignTask) FillCustomForm(ctx context.Context, form map[strin
|
||||
continue
|
||||
}
|
||||
|
||||
fieldObj := field.(map[string]*gvar.Var)
|
||||
fieldObj := gvar.New(field).MapStrVar()
|
||||
fieldTitle := fieldObj["title"].String()
|
||||
|
||||
if targetParam.Title != fieldTitle {
|
||||
@@ -607,7 +603,7 @@ func (c *CpdailyAutoSignTask) FillCustomForm(ctx context.Context, form map[strin
|
||||
var extraFieldItemValue map[string]*gvar.Var
|
||||
|
||||
for _, item := range extraFieldItems {
|
||||
itemObj := item.(map[string]*gvar.Var)
|
||||
itemObj := gvar.New(item).MapStrVar()
|
||||
content := itemObj["content"].String()
|
||||
|
||||
if content == targetParam.Content {
|
||||
|
||||
@@ -10,10 +10,9 @@ import (
|
||||
"likedream-goframe/internal/application/cpdaily/task/formtype"
|
||||
"likedream-goframe/internal/application/cpdaily/taskhelper"
|
||||
"likedream-goframe/internal/application/cpdaily/types"
|
||||
"likedream-goframe/internal/common/cst"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/cst"
|
||||
"likedream-goframe/internal/utils"
|
||||
"math/rand/v2"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -436,19 +435,27 @@ func (c *CpdailySleepCheckTask) ParseFormJson(ctx context.Context, rawJson map[s
|
||||
var list []*formtype.CpTaskFormItem
|
||||
|
||||
// 经纬度
|
||||
//dto := &formtype.CpTaskFormItem{
|
||||
// Title: "经度",
|
||||
// ColName: "lon",
|
||||
// Type: formtype.ItemTypeInput,
|
||||
// Required: true,
|
||||
//}
|
||||
//list = append(list, dto)
|
||||
//
|
||||
//dto = &formtype.CpTaskFormItem{
|
||||
// Title: "纬度",
|
||||
// ColName: "lat",
|
||||
// Type: formtype.ItemTypeInput,
|
||||
// Required: true,
|
||||
//}
|
||||
//list = append(list, dto)
|
||||
dto := &formtype.CpTaskFormItem{
|
||||
Title: "经度",
|
||||
ColName: "lon",
|
||||
Type: formtype.ItemTypeInput,
|
||||
Required: true,
|
||||
}
|
||||
list = append(list, dto)
|
||||
|
||||
dto = &formtype.CpTaskFormItem{
|
||||
Title: "纬度",
|
||||
ColName: "lat",
|
||||
Type: formtype.ItemTypeInput,
|
||||
Title: "经纬度",
|
||||
ColName: "latAndLon",
|
||||
Type: formtype.ItemTypeLatAndLon,
|
||||
Required: true,
|
||||
Tip: "您可以通过地址搜索签到经纬度,如果觉得不够准确可以自行修改",
|
||||
}
|
||||
list = append(list, dto)
|
||||
dto = &formtype.CpTaskFormItem{
|
||||
@@ -456,21 +463,20 @@ func (c *CpdailySleepCheckTask) ParseFormJson(ctx context.Context, rawJson map[s
|
||||
ColName: "address",
|
||||
Type: formtype.ItemTypeInput,
|
||||
Required: true,
|
||||
Tip: "这个就是指签到完毕后查看时最底部那个最详细的地址信息哦",
|
||||
}
|
||||
list = append(list, dto)
|
||||
|
||||
var current uint = 1
|
||||
|
||||
if isPhoto := rows["isPhoto"]; isPhoto != nil && isPhoto.Int() == 1 {
|
||||
dto = &formtype.CpTaskFormItem{
|
||||
Title: "查寝照片",
|
||||
ColName: "signImg",
|
||||
Type: formtype.ItemTypeImage,
|
||||
Required: true,
|
||||
Number: current,
|
||||
Tip: "您可以上传多张图片,会顺序轮换更新的哦~",
|
||||
}
|
||||
list = append(list, dto)
|
||||
current++
|
||||
|
||||
}
|
||||
|
||||
// 是否范围外签到
|
||||
@@ -479,7 +485,6 @@ func (c *CpdailySleepCheckTask) ParseFormJson(ctx context.Context, rawJson map[s
|
||||
ColName: "isMalposition",
|
||||
Type: formtype.ItemTypeSingle,
|
||||
Required: true,
|
||||
Number: current,
|
||||
Options: []formtype.CpTaskFormItemOption{
|
||||
{
|
||||
Content: "是",
|
||||
@@ -491,9 +496,9 @@ func (c *CpdailySleepCheckTask) ParseFormJson(ctx context.Context, rawJson map[s
|
||||
IsOtherItems: false,
|
||||
},
|
||||
},
|
||||
Tip: "一般在学校就是选否,如果在外实习、请假之类的才选是",
|
||||
}
|
||||
list = append(list, dto)
|
||||
current++
|
||||
|
||||
for key, item := range list {
|
||||
list[key].Show = item.LogicWid == ""
|
||||
@@ -502,7 +507,7 @@ func (c *CpdailySleepCheckTask) ParseFormJson(ctx context.Context, rawJson map[s
|
||||
return list
|
||||
}
|
||||
|
||||
func (c *CpdailySleepCheckTask) FillCustomForm(ctx context.Context, form map[string]*gvar.Var, paramList []*formtype.CpTaskFormItem, deviceId, username string) map[string]*gvar.Var {
|
||||
func (c *CpdailySleepCheckTask) FillCustomForm(ctx context.Context, form map[string]*gvar.Var, paramList []*formtype.CpTaskFormItem, deviceId, imgId string) map[string]*gvar.Var {
|
||||
var resultForm map[string]*gvar.Var
|
||||
|
||||
// 填充必要变量
|
||||
@@ -520,6 +525,12 @@ func (c *CpdailySleepCheckTask) FillCustomForm(ctx context.Context, form map[str
|
||||
lon = v.Content
|
||||
case "address":
|
||||
address = v.Content
|
||||
case "latAndLon":
|
||||
{
|
||||
res := strings.Split(v.Content, ",")
|
||||
lat, lon = res[1], res[0]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,7 +540,8 @@ func (c *CpdailySleepCheckTask) FillCustomForm(ctx context.Context, form map[str
|
||||
resultForm["uaIsCpadaily"] = gvar.New(true)
|
||||
|
||||
// 处理验证码
|
||||
submitCaptchaResolver := submitcaptcha.NewRuoliResolver(c.client, c.host, deviceId, types.TaskTypeSleepCheck)
|
||||
submitCaptchaResolver := submitcaptcha.NewNormalResolver(c.client, c.host, deviceId, types.TaskTypeSleepCheck)
|
||||
//submitCaptchaResolver := submitcaptcha.NewAIResolver(c.client, c.host, deviceId, types.TaskTypeSleepCheck)
|
||||
captchaInfo := submitCaptchaResolver.GetValidationInfo(ctx)
|
||||
|
||||
if captchaInfo.Validation {
|
||||
@@ -539,9 +551,10 @@ func (c *CpdailySleepCheckTask) FillCustomForm(ctx context.Context, form map[str
|
||||
return utils.Catch(func() {
|
||||
ticket = submitCaptchaResolver.GetCaptchaTicket(ctx, captchaInfo)
|
||||
})
|
||||
}, retry.MaxDelay(15*time.Second), retry.Attempts(5), retry.Context(ctx))
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "提交验证码识别失败").WithInnerError(e), oops.WithContext(ctx).Wrap)
|
||||
|
||||
}, retry.Delay(10*time.Second), retry.Attempts(5), retry.Context(ctx))
|
||||
if e != nil {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "提交验证码识别失败").WithInnerError(e), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
resultForm["ticket"] = gvar.New(ticket)
|
||||
} else {
|
||||
resultForm["ticket"] = gvar.New("")
|
||||
@@ -560,32 +573,16 @@ func (c *CpdailySleepCheckTask) FillCustomForm(ctx context.Context, form map[str
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "未找到查寝照片参数,请上传照片"), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
|
||||
imageUuidSeq := strings.SplitSeq(targetParam.Content, ",")
|
||||
|
||||
var imageUuids []string
|
||||
|
||||
for imageUuid := range imageUuidSeq {
|
||||
imageUuid = strings.TrimSpace(imageUuid)
|
||||
if imageUuid == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
imageUuids = append(imageUuids, imageUuid)
|
||||
}
|
||||
|
||||
if len(imageUuids) == 0 {
|
||||
if imgId == "" {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "未找到查寝照片参数,请上传照片"), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
|
||||
// 随机选择一张照片
|
||||
imageUuid := imageUuids[rand.IntN(len(imageUuids))]
|
||||
|
||||
imageId, e := uuid.Parse(imageUuid)
|
||||
imageId, e := uuid.Parse(imgId)
|
||||
utils.OkOrPanic(e, oops.WithContext(ctx).Wrap)
|
||||
|
||||
previewUrl := imageuploader.UploadImageFromStorage(ctx, c.host, imageId, types.TaskTypeSleepCheck, c.client)
|
||||
|
||||
resultForm["signImg"] = gvar.New(previewUrl)
|
||||
resultForm["signPhotoUrl"] = gvar.New(previewUrl)
|
||||
}
|
||||
|
||||
// 判断是否存在二维码查寝
|
||||
@@ -660,11 +657,11 @@ func (c *CpdailySleepCheckTask) Submit(ctx context.Context, form map[string]*gva
|
||||
|
||||
// 构造提交数据
|
||||
submitData := map[string]*gvar.Var{
|
||||
"lon": form["longitude"],
|
||||
"version": gvar.New(cryptoutil.EncryptVersion),
|
||||
"calVersion": gvar.New(cpdailyInfo.CalVersion),
|
||||
"deviceId": extensionForm["deviceId"],
|
||||
"userId": gvar.New(username),
|
||||
"lon": form["longitude"],
|
||||
"version": gvar.New(cryptoutil.EncryptVersion),
|
||||
"calVersion": gvar.New(cpdailyInfo.CalVersion),
|
||||
"deviceId": extensionForm["deviceId"],
|
||||
//"userId": gvar.New(username),
|
||||
"systemName": gvar.New("android"),
|
||||
"bodyString": gvar.New(bodyString),
|
||||
"lat": form["latitude"],
|
||||
@@ -674,7 +671,6 @@ func (c *CpdailySleepCheckTask) Submit(ctx context.Context, form map[string]*gva
|
||||
}
|
||||
|
||||
sign := taskhelper.GetSubmitSign(submitData)
|
||||
|
||||
submitData["sign"] = gvar.New(sign)
|
||||
response, e := c.client.R().
|
||||
SetBody(submitData).
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
)
|
||||
|
||||
const submitSignSalt = "HashSalt" // 请根据实际情况替换为真实盐值
|
||||
const submitSignSalt = "hru89rwr87Nthifa" // 请根据实际情况替换为真实盐值
|
||||
|
||||
func GetSubmitSign(form map[string]*gvar.Var) string {
|
||||
// 按照指定顺序构建哈希数据
|
||||
|
||||
@@ -2,8 +2,9 @@ package filestorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"likedream-goframe/internal/common/cst"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"likedream-goframe/internal/cst"
|
||||
)
|
||||
|
||||
var GlobalS3 *S3
|
||||
|
||||
@@ -10,11 +10,12 @@ import (
|
||||
|
||||
var Cpdaily = resty.New().
|
||||
SetCookieJar(nil).
|
||||
SetTimeout(30 * time.Second).
|
||||
SetTimeout(30*time.Second).
|
||||
SetDoNotParseResponse(true).
|
||||
SetTLSClientConfig(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
}).
|
||||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36")
|
||||
|
||||
func init() {
|
||||
if !utils.IsDebug() {
|
||||
@@ -23,3 +24,25 @@ func init() {
|
||||
Cpdaily.SetProxy("http://127.0.0.1:9000")
|
||||
}
|
||||
}
|
||||
|
||||
// NewCpdailyClient 创建一个全新的独立 client 实例,避免 Clone 可能的浅拷贝问题
|
||||
// 每个实例都是完全独立的,适用于并发场景,不会共享状态
|
||||
func NewCpdailyClient() *resty.Client {
|
||||
client := resty.New().
|
||||
SetCookieJar(nil).
|
||||
SetTimeout(30*time.Second).
|
||||
SetDoNotParseResponse(true).
|
||||
SetTLSClientConfig(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}).
|
||||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36")
|
||||
|
||||
// 根据调试模式设置代理和调试选项
|
||||
if !utils.IsDebug() {
|
||||
client.SetDebug(false)
|
||||
} else {
|
||||
client.SetProxy("http://127.0.0.1:9000")
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"time"
|
||||
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
var Music = resty.New().
|
||||
SetCookieJar(nil).
|
||||
SetTimeout(3 * time.Minute).
|
||||
SetTLSClientConfig(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
@@ -0,0 +1,48 @@
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"likedream-goframe/internal/application/cookiejar"
|
||||
"likedream-goframe/internal/utils"
|
||||
"time"
|
||||
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
var Xuexitong = resty.New().
|
||||
SetCookieJar(cookiejar.NewAllSavePersistCookieJar()).
|
||||
SetRedirectPolicy(resty.FlexibleRedirectPolicy(20)).
|
||||
SetTimeout(30 * time.Second).
|
||||
SetTLSClientConfig(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
|
||||
func init() {
|
||||
if !utils.IsDebug() {
|
||||
Xuexitong.SetDebug(false)
|
||||
} else {
|
||||
Xuexitong.SetProxy("http://127.0.0.1:9000")
|
||||
}
|
||||
}
|
||||
|
||||
// NewXuexitongClient 创建一个全新的独立 client 实例,避免 Clone 可能的浅拷贝问题
|
||||
// 每个实例都是完全独立的,适用于并发场景,不会共享状态
|
||||
func NewXuexitongClient() *resty.Client {
|
||||
client := resty.New().
|
||||
SetCookieJar(nil).
|
||||
SetTimeout(30*time.Second).
|
||||
SetDoNotParseResponse(false).
|
||||
SetTLSClientConfig(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}).
|
||||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36")
|
||||
|
||||
// 根据调试模式设置代理和调试选项
|
||||
if !utils.IsDebug() {
|
||||
client.SetDebug(false)
|
||||
} else {
|
||||
client.SetProxy("http://127.0.0.1:9000")
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package music
|
||||
|
||||
import (
|
||||
"context"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/utils"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/samber/oops"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
var MusicDownloader *Downloader
|
||||
|
||||
type Downloader struct {
|
||||
}
|
||||
|
||||
func (c *Downloader) Search(ctx context.Context, keyword string) (result []MusicSearchResult) {
|
||||
client := httpclient.Music.Clone(ctx)
|
||||
response, err := client.R().Get("https://www.gequbao.net/s/" + url.PathEscape(keyword))
|
||||
if err != nil {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "搜索出错1"), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
reader, err := goquery.NewDocumentFromReader(response.Body)
|
||||
if err != nil {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "搜索出错2"), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
card := reader.Find(".card")
|
||||
find := card.Find(".card-text > .row:not(:first-child)")
|
||||
find.Each(func(i int, item *goquery.Selection) {
|
||||
selection := item.Find("a")
|
||||
url, _ := selection.Attr("href")
|
||||
name := strings.TrimSpace(selection.Find(".text-primary span").Text())
|
||||
singer := strings.TrimSpace(selection.Find(".text-jade").Text())
|
||||
result = append(result, MusicSearchResult{
|
||||
Name: name,
|
||||
Singer: singer,
|
||||
Path: url,
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Downloader) Download(ctx context.Context, url string) string {
|
||||
client := httpclient.Music.Clone(ctx)
|
||||
response, err := client.R().Get("https://www.gequbao.net" + url)
|
||||
if err != nil {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "下载出错1"), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
html := response.String()
|
||||
reg := regexp.MustCompile("window\\.play_id = '(.*?)';")
|
||||
play_id := reg.FindStringSubmatch(html)[1]
|
||||
post, err := client.R().SetBody(map[string]string{
|
||||
"id": play_id,
|
||||
}).Post("https://www.gequbao.net/api/play-url")
|
||||
if err != nil {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "下载出错2"), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
download_url := gjson.Get(post.String(), "data.url").String()
|
||||
|
||||
// 下载音频文件
|
||||
return download_url
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package music
|
||||
|
||||
type MusicSearchResult struct {
|
||||
Name string `json:"name"`
|
||||
Singer string `json:"singer"`
|
||||
Path string `json:"url"`
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package cryptoutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"likedream-goframe/internal/framework/logger"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
KEY = "u2oh6Vu^HWe4_AES"
|
||||
)
|
||||
|
||||
func LoginEncrypt(str string) (string, error) {
|
||||
key := []byte(KEY)
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
logger.Logger.Error("aes.NewCipher err:", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
paddedData := pad([]byte(str), block.BlockSize())
|
||||
ciphertext := make([]byte, len(paddedData))
|
||||
mode := cipher.NewCBCEncrypter(block, key)
|
||||
mode.CryptBlocks(ciphertext, paddedData)
|
||||
data := base64.StdEncoding.EncodeToString(ciphertext)
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// pad 确保数据长度是块大小的整数倍,以便符合块加密算法的要求
|
||||
func pad(src []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(src)%blockSize
|
||||
padText := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(src, padText...)
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
package entity
|
||||
|
||||
// XueXiTCourse 课程所有信息
|
||||
type XueXiTCourseJson struct {
|
||||
Result int `json:"result"`
|
||||
Msg string `json:"msg"`
|
||||
ChannelList []ChannelItem `json:"channelList"`
|
||||
Mcode string `json:"mcode"`
|
||||
Createcourse int `json:"createcoursed"`
|
||||
TeacherEndCourse int `json:"teacherEndCourse"`
|
||||
ShowEndCourse int `json:"showEndCourse"`
|
||||
HasMore bool `json:"hasMore"`
|
||||
StuEndCourse int `json:"stuEndCourse"`
|
||||
}
|
||||
|
||||
// ChannelItem 课程列表
|
||||
type ChannelItem struct {
|
||||
Cfid int `json:"cfid"`
|
||||
Norder int `json:"norder"`
|
||||
CataName string `json:"cataName"`
|
||||
Cataid string `json:"cataid"`
|
||||
Id int `json:"id"`
|
||||
Cpi int `json:"cpi"`
|
||||
Key any `json:"key"`
|
||||
Content struct {
|
||||
Studentcount int `json:"studentcount"`
|
||||
Chatid string `json:"chatid"`
|
||||
IsFiled int `json:"isFiled"`
|
||||
Isthirdaq int `json:"isthirdaq"`
|
||||
Isstart bool `json:"isstart"`
|
||||
Isretire int `json:"isretire"`
|
||||
Name string `json:"name"`
|
||||
Course struct {
|
||||
Data []struct {
|
||||
BelongSchoolId string `json:"belongSchoolId"`
|
||||
Coursestate int `json:"coursestate"`
|
||||
Teacherfactor string `json:"teacherfactor"`
|
||||
IsCourseSquare int `json:"isCourseSquare"`
|
||||
Schools string `json:"schools"`
|
||||
CourseSquareUrl string `json:"courseSquareUrl"`
|
||||
Imageurl string `json:"imageurl"`
|
||||
AppInfo string `json:"appInfo"`
|
||||
Name string `json:"name"`
|
||||
DefaultShowCatalog int `json:"defaultShowCatalog"`
|
||||
Id int `json:"id"`
|
||||
AppData int `json:"appData"`
|
||||
} `json:"data"`
|
||||
} `json:"course"`
|
||||
Roletype int `json:"roletype"`
|
||||
Id int `json:"id"`
|
||||
State int `json:"state"`
|
||||
Cpi int `json:"cpi"`
|
||||
Bbsid string `json:"bbsid"`
|
||||
IsSquare int `json:"isSquare"`
|
||||
} `json:"content"`
|
||||
Topsign int `json:"topsign"`
|
||||
}
|
||||
|
||||
type XueXiTCourse struct {
|
||||
Cpi int `json:"cpi"` // 用户唯一标识
|
||||
Key string `json:"key"` // classID 在课程API中为key
|
||||
CourseID string `json:"courseId"` // 课程ID
|
||||
ChatID string `json:"chatId"`
|
||||
CourseTeacher string `json:"courseTeacher"` // 课程老师
|
||||
CourseName string `json:"courseName"` //课程名
|
||||
CourseImage string `json:"courseImage"`
|
||||
// 两个标识 暂时不知道有什么用
|
||||
CourseDataID int `json:"courseDataId"`
|
||||
ContentID int `json:"ContentID"`
|
||||
IsStart bool `json:"isstart"` //是否开课了,开课了为true,没开课为false。一般来说开课才能刷,不然刷不了的
|
||||
State int `json:"state"` //课程状态,0为正常,1为课程已经结束
|
||||
}
|
||||
|
||||
// XueXiTCourseChapter 课程章节信息
|
||||
type XueXiTCourseChapter struct {
|
||||
ID string `json:"id"` // 章节ID
|
||||
Name string `json:"name"` // 章节名称
|
||||
ParentNodeID string `json:"parentnodeid"` // 父节点ID
|
||||
Layer int `json:"layer"` // 层级
|
||||
Label string `json:"label"` // 标签
|
||||
IndexOrder int `json:"indexOrder"` // 排序索引
|
||||
JobCount int `json:"jobcount"` // 任务点数量
|
||||
Status int `json:"status"` // 状态
|
||||
IsReview int `json:"isReview"` // 是否复习
|
||||
BeginTime int64 `json:"begintime"` // 开始时间
|
||||
EndTime int64 `json:"endtime"` // 结束时间
|
||||
Attachment []XueXiTAttachment `json:"attachment"` // 附件列表
|
||||
Knowledge []XueXiTCourseChapter `json:"knowledge"` // 子章节列表
|
||||
}
|
||||
|
||||
// XueXiTAttachment 附件信息
|
||||
type XueXiTAttachment struct {
|
||||
ID string `json:"id"` // 附件ID
|
||||
Type string `json:"type"` // 附件类型(video/document等)
|
||||
ObjectID string `json:"objectid"` // 对象ID
|
||||
Extension string `json:"extension"` // 扩展信息
|
||||
JobID string `json:"jobid"` // 任务ID
|
||||
Property string `json:"property"` // 属性
|
||||
Name string `json:"name"` // 附件名称
|
||||
IsPassed bool `json:"isPassed"` // 是否已完成
|
||||
}
|
||||
|
||||
// ChapterData 章节数据响应
|
||||
type ChapterData struct {
|
||||
ID string `json:"id"`
|
||||
BbsID string `json:"bbsid"`
|
||||
ClassScore int `json:"classscore"`
|
||||
IsStart int `json:"isstart"`
|
||||
AllowDownload int `json:"allowdownload"`
|
||||
ChatID string `json:"chatid"`
|
||||
Name string `json:"name"`
|
||||
State int `json:"state"`
|
||||
IsFiled int `json:"isfiled"`
|
||||
VisibleScore int `json:"visiblescore"`
|
||||
HideClazz int `json:"hideclazz"`
|
||||
BeginDate int64 `json:"begindate"`
|
||||
CourseSetting struct {
|
||||
ID string `json:"id"`
|
||||
CourseID string `json:"courseid"`
|
||||
HiddenCourseCover int `json:"hiddencoursecover"`
|
||||
CourseFaceCheck int `json:"coursefacecheck"`
|
||||
} `json:"coursesetting"`
|
||||
Course struct {
|
||||
ID string `json:"id"`
|
||||
BelongSchoolID string `json:"belongschoolid"`
|
||||
Name string `json:"name"`
|
||||
InfoContent string `json:"infocontent"`
|
||||
ObjectID string `json:"objectid"`
|
||||
App int `json:"app"`
|
||||
BulletFormat string `json:"bulletformat"`
|
||||
MappingCourseID string `json:"mappingcourseid"`
|
||||
ImageURL string `json:"imageurl"`
|
||||
TeacherFactor string `json:"teacherfactor"`
|
||||
JobCount int `json:"jobcount"`
|
||||
Knowledge []XueXiTCourseChapter `json:"knowledge"`
|
||||
} `json:"course"`
|
||||
}
|
||||
|
||||
// Card 任务点卡片信息
|
||||
type Card struct {
|
||||
ID string `json:"id"` // 卡片ID
|
||||
Title string `json:"title"` // 标题
|
||||
Description string `json:"description"` // 描述
|
||||
KnowledgeID string `json:"knowledgeid"` // 知识点ID
|
||||
Type string `json:"type"` // 类型(video/document/work等)
|
||||
IsPassed bool `json:"isPassed"` // 是否完成
|
||||
ObjectID string `json:"objectid"` // 对象ID
|
||||
JobID string `json:"jobid"` // 任务ID
|
||||
ResourceID string `json:"resourceid"` // 资源ID
|
||||
Attachment *IframeAttributes `json:"attachment,omitempty"` // iframe属性
|
||||
}
|
||||
|
||||
// IframeAttributes iframe属性
|
||||
type IframeAttributes struct {
|
||||
Src string `json:"src"` // iframe地址
|
||||
Module string `json:"module"` // 模块名
|
||||
ModToken string `json:"modtoken"` // 模块token
|
||||
Width string `json:"width"` // 宽度
|
||||
Height string `json:"height"` // 高度
|
||||
Scrolling string `json:"scrolling"` // 滚动
|
||||
ClassID string `json:"classid"` // 课程ID
|
||||
ObjectID string `json:"objectid"` // 对象ID
|
||||
Enc string `json:"enc"` // 加密参数
|
||||
UtEnc string `json:"utenc"` // ut加密参数
|
||||
OtherInfo string `json:"otherinfo"` // 其他信息
|
||||
JobID string `json:"jobid"` // 任务ID
|
||||
KnowledgeID string `json:"knowledgeid"` // 知识点ID
|
||||
CourseFaceCheck int `json:"coursefacecheck"` // 人脸识别
|
||||
}
|
||||
|
||||
// DataItem 任务点数据项
|
||||
type DataItem struct {
|
||||
ID string `json:"id"`
|
||||
IndexOrder int `json:"indexorder"`
|
||||
KnowledgeID string `json:"knowledgeid"`
|
||||
Attachment struct {
|
||||
ObjectID string `json:"objectid"`
|
||||
Property struct {
|
||||
Name string `json:"name"`
|
||||
IsPassed bool `json:"ispassed"`
|
||||
JobID string `json:"jobid"`
|
||||
} `json:"property"`
|
||||
Type string `json:"type"`
|
||||
} `json:"attachment"`
|
||||
PointList []interface{} `json:"pointList"`
|
||||
JobCount int `json:"jobcount"`
|
||||
Duration int64 `json:"duration"`
|
||||
ClazzID string `json:"clazzid"`
|
||||
CourseID string `json:"courseid"`
|
||||
Module string `json:"module"`
|
||||
}
|
||||
|
||||
// VideoDto 视频信息
|
||||
type VideoDto struct {
|
||||
VideoPath string `json:"videoPath"` // 视频路径
|
||||
ObjectID string `json:"objectid"` // 对象ID
|
||||
OtherInfo string `json:"otherInfo"` // 其他信息
|
||||
JobID string `json:"jobid"` // 任务ID
|
||||
Duration int `json:"duration"` // 时长(秒)
|
||||
DToken string `json:"dtoken"` // dtoken
|
||||
IsAudio bool `json:"isAudio"` // 是否是音频
|
||||
IsPassed bool `json:"isPassed"` // 是否已完成
|
||||
}
|
||||
|
||||
// DocumentDto 文档信息
|
||||
type DocumentDto struct {
|
||||
ObjectID string `json:"objectid"` // 对象ID
|
||||
JobID string `json:"jobid"` // 任务ID
|
||||
OtherInfo string `json:"otherInfo"` // 其他信息
|
||||
JToken string `json:"jtoken"` // jtoken
|
||||
PageCount int `json:"pageCount"` // 页数
|
||||
IsPassed bool `json:"isPassed"` // 是否已完成
|
||||
}
|
||||
|
||||
// FaceQRCodeResponse 人脸识别二维码响应
|
||||
type FaceQRCodeResponse struct {
|
||||
Status bool `json:"status"`
|
||||
Msg string `json:"msg"`
|
||||
QR string `json:"qr"` // 二维码base64数据
|
||||
Enc string `json:"enc"` // 加密参数
|
||||
}
|
||||
|
||||
// FaceTokenResponse 人脸识别上传令牌响应
|
||||
type FaceTokenResponse struct {
|
||||
Result int `json:"result"`
|
||||
UploadToken string `json:"uploadtoken"`
|
||||
}
|
||||
|
||||
// FaceUploadResponse 人脸识别上传响应
|
||||
type FaceUploadResponse struct {
|
||||
Result bool `json:"result"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
FileID string `json:"fileId"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// FaceVerifyResponse 人脸识别验证响应
|
||||
type FaceVerifyResponse struct {
|
||||
Status bool `json:"status"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
// WorkDto 作业信息
|
||||
type WorkDto struct {
|
||||
WorkID string `json:"workid"` // 作业ID
|
||||
CourseID string `json:"courseid"` // 课程ID
|
||||
ClassID string `json:"classid"` // 班级ID
|
||||
KnowledgeID string `json:"knowledgeid"` // 知识点ID
|
||||
JobID string `json:"jobid"` // 任务ID
|
||||
Enc string `json:"enc"` // 加密参数
|
||||
UTEnc string `json:"utenc"` // UT加密参数
|
||||
Title string `json:"title"` // 作业标题
|
||||
IsPassed bool `json:"isPassed"` // 是否已完成
|
||||
Questions []WorkQuestion `json:"questions"` // 题目列表
|
||||
}
|
||||
|
||||
// WorkQuestion 作业题目
|
||||
type WorkQuestion struct {
|
||||
QuestionID string `json:"questionId"` // 题目ID
|
||||
Type int `json:"type"` // 题目类型: 0单选 1多选 2判断 3填空 4简答
|
||||
Title string `json:"title"` // 题目标题
|
||||
Description string `json:"description"` // 题目描述
|
||||
Options []string `json:"options"` // 选项列表(单选/多选/判断)
|
||||
Answer string `json:"answer"` // 答案
|
||||
Score float64 `json:"score"` // 分值
|
||||
}
|
||||
|
||||
// WorkSubmitRequest 作业提交请求
|
||||
type WorkSubmitRequest struct {
|
||||
WorkID string `json:"workid"`
|
||||
TotalScore string `json:"totalScore"`
|
||||
AnswerList []WorkAnswerItem `json:"answerlist"`
|
||||
UserId string `json:"userId"`
|
||||
ClassId string `json:"classId"`
|
||||
CourseId string `json:"courseId"`
|
||||
KnowledgeId string `json:"knowledgeid"`
|
||||
JobId string `json:"jobid"`
|
||||
Enc string `json:"enc"`
|
||||
UTEnc string `json:"utenc"`
|
||||
}
|
||||
|
||||
// WorkAnswerItem 作业答案项
|
||||
type WorkAnswerItem struct {
|
||||
QuestionID string `json:"questionId"`
|
||||
Answer string `json:"answer"`
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package xuexitong
|
||||
|
||||
import (
|
||||
"context"
|
||||
"likedream-goframe/internal/application/cookiejar"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
"likedream-goframe/internal/application/xuexitong/cryptoutil"
|
||||
"likedream-goframe/internal/framework/logger"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
ApiLoginWeb = "https://passport2.chaoxing.com/fanyalogin"
|
||||
)
|
||||
|
||||
type XuexitongLogin struct {
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewXuexitongLogin(ctx context.Context) *XuexitongLogin {
|
||||
return &XuexitongLogin{
|
||||
client: httpclient.NewXuexitongClient(),
|
||||
}
|
||||
}
|
||||
func (c *XuexitongLogin) Login(ctx context.Context, username, password string, retry int) (err error) {
|
||||
jar := cookiejar.NewAllSavePersistCookieJar()
|
||||
c.client.SetCookieJar(jar)
|
||||
usernameEncrypted, err := cryptoutil.LoginEncrypt(username)
|
||||
if err != nil {
|
||||
logger.Logger.Error("LoginEncrypt err:", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
passwordEncrypted, err := cryptoutil.LoginEncrypt(password)
|
||||
if err != nil {
|
||||
logger.Logger.Error("LoginEncrypt err:", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
resp, err := c.client.R().SetFormData(map[string]string{
|
||||
"fid": "-1",
|
||||
"uname": usernameEncrypted,
|
||||
"password": passwordEncrypted,
|
||||
"refer": "http%3A%2F%2Fi.mooc.chaoxing.com",
|
||||
"t": "true",
|
||||
"forbidotherlogin": "0",
|
||||
"validate": "",
|
||||
"doubleFactorLogin": "0",
|
||||
"independentId": "0",
|
||||
"independentNameId": "0",
|
||||
}).Post(ApiLoginWeb)
|
||||
if err != nil {
|
||||
logger.Logger.Error("LoginPost err:", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
body := resp.String()
|
||||
//触发风控直接重新登录
|
||||
if strings.Contains(body, "很抱歉,您所浏览的页面暂时不能访问!") && retry > 0 {
|
||||
time.Sleep(time.Duration(retry*5) * time.Second)
|
||||
return c.Login(ctx, username, password, retry-1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *XuexitongLogin) GetCookieJar() http.CookieJar {
|
||||
return c.client.CookieJar()
|
||||
}
|
||||
func (c *XuexitongLogin) GetCookieString() string {
|
||||
cookieJar := c.GetCookieJar()
|
||||
serializableJar := cookieJar.(*cookiejar.PersistCookieJar)
|
||||
return serializableJar.Serialize()
|
||||
}
|
||||
func (c *XuexitongLogin) GetClient() *resty.Client {
|
||||
return c.client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package xuexitong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
var randChar []string = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "A", "B", "C", "D", "E", "F"}
|
||||
|
||||
func ApiVerifyCodeBytes(client *resty.Client) ([]byte, error) {
|
||||
|
||||
urlStr := "https://mooc1-api.chaoxing.com/processVerifyPng.ac?t=" + fmt.Sprintf("%d", time.Now().UnixMilli())
|
||||
|
||||
req := client.R()
|
||||
req.Header.Add("User-Agent", GetUA("mobile"))
|
||||
req.Header.Add("Accept", "*/*")
|
||||
req.Header.Add("Host", "mooc1-api.chaoxing.com")
|
||||
req.Header.Add("Connection", "keep-alive")
|
||||
res, err := req.Get(urlStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
all, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return all, nil
|
||||
}
|
||||
|
||||
func ApiPassVerifyCode(client *resty.Client, code string, retry int) bool {
|
||||
if retry < 0 {
|
||||
return false
|
||||
}
|
||||
urlStr := "https://mooc1-api.chaoxing.com/html/processVerify.ac"
|
||||
client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
req := client.R()
|
||||
req.SetMultipartField("ucode", "", "", strings.NewReader(code))
|
||||
req.SetMultipartField("app", "", "", strings.NewReader("0"))
|
||||
req.Header.Add("User-Agent", GetUA("mobile"))
|
||||
req.Header.Add("Accept", "*/*")
|
||||
req.Header.Add("Host", "mooc1-api.chaoxing.com")
|
||||
req.Header.Add("Connection", "keep-alive")
|
||||
res, err := req.Post(urlStr)
|
||||
if err != nil {
|
||||
return ApiPassVerifyCode(client, code, retry-1)
|
||||
}
|
||||
client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
return res.StatusCode() == 302
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,253 @@
|
||||
package xuexitong
|
||||
|
||||
import (
|
||||
"context"
|
||||
"likedream-goframe/internal/application/xuexitong/entity"
|
||||
"likedream-goframe/internal/utils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestXuexitongLogin(t *testing.T) {
|
||||
xuexitongLogin := NewXuexitongLogin(context.Background())
|
||||
err := xuexitongLogin.Login(context.Background(), "13551503012", "xiaozhi520", 5)
|
||||
utils.OkOrPanic(err)
|
||||
}
|
||||
|
||||
func TestPullCourse(t *testing.T) {
|
||||
xuexitongLogin := NewXuexitongLogin(context.Background())
|
||||
err := xuexitongLogin.Login(context.Background(), "13551503012", "xiaozhi520", 5)
|
||||
utils.OkOrPanic(err)
|
||||
client := xuexitongLogin.GetClient()
|
||||
courseAction := NewXuexitongCourse(client)
|
||||
list := courseAction.PullCourseList(context.Background())
|
||||
utils.OkOrPanic(err)
|
||||
t.Log(list)
|
||||
}
|
||||
|
||||
func TestPullChapter(t *testing.T) {
|
||||
xuexitongLogin := NewXuexitongLogin(context.Background())
|
||||
err := xuexitongLogin.Login(context.Background(), "13551503012", "xiaozhi520", 5)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
client := xuexitongLogin.GetClient()
|
||||
courseAction := NewXuexitongCourse(client)
|
||||
|
||||
// 先获取课程列表
|
||||
courseList := courseAction.PullCourseList(context.Background())
|
||||
if len(courseList) == 0 {
|
||||
t.Fatal("没有找到课程")
|
||||
}
|
||||
|
||||
// 获取第一个课程的章节
|
||||
chapters := courseAction.PullCourseChapterList(context.Background(), courseList[0].CourseID)
|
||||
t.Logf("课程 %s 的章节数量: %d", courseList[0].CourseName, len(chapters))
|
||||
|
||||
for _, chapter := range chapters {
|
||||
t.Logf("章节: %s, 任务点数量: %d", chapter.Name, chapter.JobCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchChapterCards(t *testing.T) {
|
||||
xuexitongLogin := NewXuexitongLogin(context.Background())
|
||||
err := xuexitongLogin.Login(context.Background(), "13551503012", "xiaozhi520", 5)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
client := xuexitongLogin.GetClient()
|
||||
courseAction := NewXuexitongCourse(client)
|
||||
|
||||
// 获取课程列表
|
||||
courseList := courseAction.PullCourseList(context.Background())
|
||||
if len(courseList) == 0 {
|
||||
t.Fatal("没有找到课程")
|
||||
}
|
||||
|
||||
course := courseList[0]
|
||||
|
||||
// 获取章节列表
|
||||
chapters := courseAction.PullCourseChapterList(context.Background(), course.CourseID)
|
||||
if len(chapters) == 0 {
|
||||
t.Fatal("没有找到章节")
|
||||
}
|
||||
|
||||
// 找一个有任务点的章节
|
||||
var targetChapter *entity.XueXiTCourseChapter
|
||||
for i := range chapters {
|
||||
if chapters[i].JobCount > 0 {
|
||||
targetChapter = &chapters[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetChapter == nil {
|
||||
t.Fatal("没有找到有任务点的章节")
|
||||
}
|
||||
|
||||
// 获取任务点卡片
|
||||
cards, err := courseAction.FetchChapterCards(context.Background(), course, targetChapter.ID, targetChapter.ID)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
t.Logf("章节 %s 的任务点数量: %d", targetChapter.Name, len(cards))
|
||||
|
||||
for _, card := range cards {
|
||||
t.Logf("任务点: 类型=%s, JobID=%s, ObjectID=%s", card.Type, card.JobID, card.ObjectID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoPlayVideo(t *testing.T) {
|
||||
xuexitongLogin := NewXuexitongLogin(context.Background())
|
||||
err := xuexitongLogin.Login(context.Background(), "13551503012", "xiaozhi520", 5)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
client := xuexitongLogin.GetClient()
|
||||
courseAction := NewXuexitongCourse(client)
|
||||
|
||||
// 获取课程列表
|
||||
courseList := courseAction.PullCourseList(context.Background())
|
||||
if len(courseList) == 0 {
|
||||
t.Fatal("没有找到课程")
|
||||
}
|
||||
|
||||
course := courseList[0]
|
||||
|
||||
// 获取章节列表
|
||||
chapters := courseAction.PullCourseChapterList(context.Background(), course.CourseID)
|
||||
if len(chapters) == 0 {
|
||||
t.Fatal("没有找到章节")
|
||||
}
|
||||
|
||||
// 找一个有任务点的章节
|
||||
var targetChapter *entity.XueXiTCourseChapter
|
||||
for i := range chapters {
|
||||
if chapters[i].JobCount > 0 {
|
||||
targetChapter = &chapters[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetChapter == nil {
|
||||
t.Fatal("没有找到有任务点的章节")
|
||||
}
|
||||
|
||||
// 获取任务点卡片
|
||||
cards, err := courseAction.FetchChapterCards(context.Background(), course, targetChapter.ID, targetChapter.ID)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
// 找视频任务点
|
||||
for _, card := range cards {
|
||||
if card.Type == "video" {
|
||||
t.Logf("开始处理视频任务点: %s", card.JobID)
|
||||
|
||||
// 获取视频信息
|
||||
videoDto, err := courseAction.FetchVideoDto(context.Background(), card.ObjectID, "", card.JobID)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
// 自动播放视频
|
||||
err = courseAction.AutoPlayVideo(context.Background(), course, card, videoDto)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
t.Logf("视频任务点完成: %s", card.JobID)
|
||||
break // 只测试一个视频
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoReadDocument(t *testing.T) {
|
||||
xuexitongLogin := NewXuexitongLogin(context.Background())
|
||||
err := xuexitongLogin.Login(context.Background(), "13551503012", "xiaozhi520", 5)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
client := xuexitongLogin.GetClient()
|
||||
courseAction := NewXuexitongCourse(client)
|
||||
|
||||
// 获取课程列表
|
||||
courseList := courseAction.PullCourseList(context.Background())
|
||||
if len(courseList) == 0 {
|
||||
t.Fatal("没有找到课程")
|
||||
}
|
||||
|
||||
course := courseList[0]
|
||||
|
||||
// 获取章节列表
|
||||
chapters := courseAction.PullCourseChapterList(context.Background(), course.CourseID)
|
||||
if len(chapters) == 0 {
|
||||
t.Fatal("没有找到章节")
|
||||
}
|
||||
|
||||
// 找一个有任务点的章节
|
||||
var targetChapter *entity.XueXiTCourseChapter
|
||||
for i := range chapters {
|
||||
if chapters[i].JobCount > 0 {
|
||||
targetChapter = &chapters[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetChapter == nil {
|
||||
t.Fatal("没有找到有任务点的章节")
|
||||
}
|
||||
|
||||
// 获取任务点卡片
|
||||
cards, err := courseAction.FetchChapterCards(context.Background(), course, targetChapter.ID, targetChapter.ID)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
// 找文档任务点
|
||||
for _, card := range cards {
|
||||
if card.Type == "document" {
|
||||
t.Logf("开始处理文档任务点: %s", card.JobID)
|
||||
|
||||
// 获取文档信息
|
||||
documentDto, err := courseAction.FetchDocumentDto(context.Background(), card.ObjectID, "", card.JobID)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
// 自动阅读文档
|
||||
err = courseAction.AutoReadDocument(context.Background(), course, card, documentDto)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
t.Logf("文档任务点完成: %s", card.JobID)
|
||||
break // 只测试一个文档
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestAutoStudyByCourseID 测试通过课程ID刷课(定时任务使用)
|
||||
func TestAutoStudyByCourseID(t *testing.T) {
|
||||
// 登录
|
||||
xuexitongLogin := NewXuexitongLogin(context.Background())
|
||||
err := xuexitongLogin.Login(context.Background(), "13551503012", "xiaozhi520", 5)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
// 创建课程实例
|
||||
courseAction := NewXuexitongCourse(xuexitongLogin.GetClient())
|
||||
|
||||
// 先获取课程列表,拿到第一个课程的ID
|
||||
courseList := courseAction.PullCourseList(context.Background())
|
||||
if len(courseList) == 0 {
|
||||
t.Fatal("没有找到课程")
|
||||
}
|
||||
|
||||
courseID := courseList[0].CourseID
|
||||
t.Logf("准备刷课程ID: %s", courseID)
|
||||
|
||||
// 一键刷指定课程的所有任务(定时任务就调用这一个方法)
|
||||
err = courseAction.AutoStudyByCourseID(context.Background(), courseID)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
t.Log("指定课程刷课完成")
|
||||
}
|
||||
|
||||
// TestAutoStudyAllCourses 测试自动刷所有课程
|
||||
func TestAutoStudyAllCourses(t *testing.T) {
|
||||
// 登录
|
||||
xuexitongLogin := NewXuexitongLogin(context.Background())
|
||||
err := xuexitongLogin.Login(context.Background(), "13551503012", "xiaozhi520", 5)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
// 创建课程实例
|
||||
courseAction := NewXuexitongCourse(xuexitongLogin.GetClient())
|
||||
|
||||
// 一键刷所有课程
|
||||
err = courseAction.AutoStudyAllCourses(context.Background())
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
t.Log("所有课程刷课完成")
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package xuexitong
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
APP_VERSION = "6.6.4"
|
||||
DEVICE_VENDOR = "MI10"
|
||||
BUILD = "10831_263"
|
||||
//BUILD = "10903_285"
|
||||
ANDROID_VERSION = "Android 11"
|
||||
)
|
||||
|
||||
var IMEI = TokenHex(16)
|
||||
|
||||
// GetUA 构建并获取 UA
|
||||
func GetUA(uaType string) string {
|
||||
switch uaType {
|
||||
case "mobile":
|
||||
//return strings.Join([]string{
|
||||
// fmt.Sprintf("Dalvik/2.1.0 (Linux; U; %s; %s Build/SKQ1.210216.001)", ANDROID_VERSION, DEVICE_VENDOR),
|
||||
// fmt.Sprintf("(schild:%s)", MobileUASign(DEVICE_VENDOR, "zh_CN", APP_VERSION, BUILD, IMEI)),
|
||||
// fmt.Sprintf("(device:%s)", DEVICE_VENDOR),
|
||||
// "Language/zh_CN",
|
||||
// fmt.Sprintf("com.chaoxing.mobile/ChaoXingStudy_3_%s_android_phone_%s", APP_VERSION, BUILD),
|
||||
// //APP_VERSION,
|
||||
// fmt.Sprintf("(@Kalimdor)_%s", IMEI),
|
||||
//}, " ")
|
||||
|
||||
return strings.Join([]string{
|
||||
fmt.Sprintf("Mozilla/5.0 (Linux; %s; %s Build/OPM1.171019.019; wv)", ANDROID_VERSION, DEVICE_VENDOR),
|
||||
fmt.Sprintf("AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36"),
|
||||
fmt.Sprintf("(schild:%s)", MobileUASign(DEVICE_VENDOR, "zh_CN", APP_VERSION, BUILD, IMEI)),
|
||||
fmt.Sprintf("(device:%s)", DEVICE_VENDOR),
|
||||
"Language/zh_CN",
|
||||
fmt.Sprintf("com.chaoxing.mobile/ChaoXingStudy_3_%s_android_phone_%s", APP_VERSION, BUILD),
|
||||
fmt.Sprintf("(@Kalimdor)_%s", IMEI),
|
||||
}, " ")
|
||||
case "web":
|
||||
return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.35"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// 用于生成IMEI
|
||||
func TokenHex(len int) string {
|
||||
b := make([]byte, 16)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
token := hex.EncodeToString(b)
|
||||
return token
|
||||
}
|
||||
|
||||
func MobileUASign(model, locale, version, build, imei string) string {
|
||||
// 拼接字符串,和 Python 的 " ".join 相同
|
||||
str := strings.Join([]string{
|
||||
"(schild:ipL$TkeiEmfy1gTXb2XHrdLN0a@7c^vu)",
|
||||
fmt.Sprintf("(device:%s)", model),
|
||||
fmt.Sprintf("Language/%s", locale),
|
||||
fmt.Sprintf("com.chaoxing.mobile/ChaoXingStudy_3_%s_android_phone_%s", version, build),
|
||||
fmt.Sprintf("(@Kalimdor)_%s", imei),
|
||||
}, " ")
|
||||
|
||||
// 计算 md5
|
||||
hash := md5.Sum([]byte(str))
|
||||
|
||||
// 转换为小写 hex 字符串
|
||||
return fmt.Sprintf("%x", hash)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package cst
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
ConfigOcrServerUrl = "ocr.url"
|
||||
ConfigEnvMode = "env.mode"
|
||||
ConfigDatabaseType = "database.type"
|
||||
ConfigDatabaseDsn = "database.dsn"
|
||||
ConfigDatabaseDsn2 = "database.dsn2"
|
||||
ConfigServerPort = "server.port"
|
||||
ConfigEmailConfirmBaseUrl = "server.emailConfirmBaseUrl"
|
||||
ConfigEmailResetPasswordUrl = "server.emailResetPasswordUrl"
|
||||
ConfigBalanceMigrateUrl = "server.balanceMigrateUrl"
|
||||
ConfigAuthorizationKey = "server.authorization"
|
||||
ConfigJwtKey = "server.jwt"
|
||||
ConfigCaptchaBaseUrl = "captcha.baseUrl"
|
||||
ConfigS3AccessKey = "s3.accessKey"
|
||||
ConfigS3SecretKey = "s3.secretKey"
|
||||
ConfigS3BaseUrl = "s3.baseUrl"
|
||||
ConfigS3Bucket = "s3.bucket"
|
||||
ConfigCpdailyAppVersion = "cpdaily.appVersion"
|
||||
ConfigCpdailySystemVersion = "cpdaily.systemVersion"
|
||||
ConfigCpdailyCalVersion = "cpdaily.calVersion"
|
||||
ConfigSliderSchoolIds = "cpdaily.sliderIds"
|
||||
|
||||
ConfigRedisUrl = "redis.url"
|
||||
|
||||
ConfigTaskConcurrency = "task.concurrency"
|
||||
|
||||
// Email配置常量
|
||||
ConfigEmailSMTPHost = "email.smtpHost"
|
||||
ConfigEmailSMTPPort = "email.smtpPort"
|
||||
ConfigEmailSMTPUsername = "email.smtpUsername"
|
||||
ConfigEmailSMTPPassword = "email.smtpPassword"
|
||||
ConfigEmailFromEmail = "email.fromEmail"
|
||||
ConfigEmailFromName = "email.fromName"
|
||||
|
||||
// 基础积分配置
|
||||
ConfigBasePointCpdaily = "basePoint.cpdaily"
|
||||
)
|
||||
|
||||
const (
|
||||
// 应用程序常量
|
||||
AppName = "若梦系统"
|
||||
AppCopyright = "© 2025 若梦系统. 保留所有权利."
|
||||
)
|
||||
|
||||
var (
|
||||
TimeZoneShangHai, _ = time.LoadLocation("Asia/Shanghai")
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
package cst
|
||||
|
||||
const (
|
||||
RedisUserInfo = "userinfo:"
|
||||
RedisVerifyCode = "verifycode:"
|
||||
RedisResetPassword = "resetpassword:"
|
||||
RedisBalanceMigrate = "balancemigrate:" // 积分转移token
|
||||
RedisCpUserLogin = "cpuserlogin:"
|
||||
RedisCpUserAuthorize = "cpuser:authorize:"
|
||||
RedisSchoolInfo = "cpuser:schoolinfo:"
|
||||
)
|
||||
|
||||
var (
|
||||
RedisUserInfoExpire = int64(86400)
|
||||
RedisVerifyCodeExpire = int64(86400)
|
||||
RedisResetPasswordExpire = int64(3600) // 重置密码链接1小时有效
|
||||
RedisBalanceMigrateExpire = int64(3600) // 积分转移链接1小时有效
|
||||
RedisCpUserLoginExpire = int64(180)
|
||||
RedisCpUserAuthorizeExpire = int64(1800)
|
||||
RedisSchoolInfoExpire = int64(3600) // 学校信息1小时有效
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
package cst
|
||||
|
||||
const (
|
||||
TaskCpdaily = "task:cpdaily"
|
||||
TaskEmail = "task:email"
|
||||
TaskResetDaily = "task:reset:daily"
|
||||
|
||||
TaskCpdailyId = "cpdaily:task:schedule:%d"
|
||||
|
||||
TaskQueueCpdaily = "queue:cpdaily"
|
||||
TaskQueueEmail = "queue:email"
|
||||
TaskQueueDefault = "default"
|
||||
)
|
||||
@@ -2,17 +2,18 @@ package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/shopspring/decimal"
|
||||
"likedream-goframe/internal/cst"
|
||||
"likedream-goframe/internal/common/cst"
|
||||
"likedream-goframe/internal/ent/sysmenu"
|
||||
"likedream-goframe/internal/ent/sysrole"
|
||||
"likedream-goframe/internal/ent/sysuser"
|
||||
"likedream-goframe/internal/utils"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
func CheckHasData() bool {
|
||||
count, err := Global.SysUser.Query().Count(context.Background())
|
||||
count, err := Global.Debug().SysUser.Query().Count(context.Background())
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return err
|
||||
})
|
||||
@@ -35,7 +36,7 @@ func GenerateData() {
|
||||
// 生成用户
|
||||
func generateUser() {
|
||||
ctx := gctx.New()
|
||||
Global.SysUser.Create().
|
||||
Global.Debug().SysUser.Create().
|
||||
SetUsername("admin").
|
||||
SetPassword(utils.PasswordBcryptHash("123456")).
|
||||
SetEmail("admin@qq.com").
|
||||
@@ -49,15 +50,15 @@ func generateUser() {
|
||||
// 生成角色
|
||||
func generateRoles() {
|
||||
ctx := gctx.New()
|
||||
Global.SysRole.Create().SetName("管理员").SetLabel(cst.KeyRoleAdmin).SetDescription("超级管理员角色").SetOrder(1).ExecX(ctx)
|
||||
Global.SysRole.Create().SetName("代理").SetLabel(cst.KeyRoleAgent).SetDescription("代理角色").SetOrder(2).ExecX(ctx)
|
||||
Global.SysRole.Create().SetName("普通用户").SetLabel(cst.KeyRoleUser).SetDescription("普通用户角色").SetOrder(3).ExecX(ctx)
|
||||
Global.Debug().SysRole.Create().SetName("管理员").SetLabel(cst.KeyRoleAdmin).SetDescription("超级管理员角色").SetOrder(1).ExecX(ctx)
|
||||
Global.Debug().SysRole.Create().SetName("代理").SetLabel(cst.KeyRoleAgent).SetDescription("代理角色").SetOrder(2).ExecX(ctx)
|
||||
Global.Debug().SysRole.Create().SetName("普通用户").SetLabel(cst.KeyRoleUser).SetDescription("普通用户角色").SetOrder(3).ExecX(ctx)
|
||||
}
|
||||
|
||||
// 生成菜单
|
||||
func generateMenus() {
|
||||
ctx := gctx.New()
|
||||
sysM := Global.SysMenu.Create().
|
||||
sysM := Global.Debug().SysMenu.Create().
|
||||
SetName("系统管理").
|
||||
SetLabel("system").
|
||||
SetType(sysmenu.TypeGroup).
|
||||
@@ -65,7 +66,7 @@ func generateMenus() {
|
||||
SetRoute("/system").
|
||||
SetOrder(1).
|
||||
SaveX(ctx)
|
||||
userM := Global.SysMenu.Create().
|
||||
userM := Global.Debug().SysMenu.Create().
|
||||
SetName("用户管理").
|
||||
SetLabel("user").
|
||||
SetType(sysmenu.TypeMenu).
|
||||
@@ -76,7 +77,7 @@ func generateMenus() {
|
||||
SetParent(sysM).
|
||||
SaveX(ctx)
|
||||
// 新增 编辑 删除按钮
|
||||
Global.SysMenu.Create().
|
||||
Global.Debug().SysMenu.Create().
|
||||
SetName("新增").
|
||||
SetLabel("add").
|
||||
SetType(sysmenu.TypeButton).
|
||||
@@ -84,7 +85,7 @@ func generateMenus() {
|
||||
SetOrder(1).
|
||||
SetParent(userM).
|
||||
SaveX(ctx)
|
||||
Global.SysMenu.Create().
|
||||
Global.Debug().SysMenu.Create().
|
||||
SetName("编辑").
|
||||
SetLabel("edit").
|
||||
SetType(sysmenu.TypeButton).
|
||||
@@ -93,7 +94,7 @@ func generateMenus() {
|
||||
SetParent(userM).
|
||||
SaveX(ctx)
|
||||
// 删除按钮
|
||||
Global.SysMenu.Create().
|
||||
Global.Debug().SysMenu.Create().
|
||||
SetName("删除").
|
||||
SetLabel("delete").
|
||||
SetType(sysmenu.TypeButton).
|
||||
@@ -102,7 +103,7 @@ func generateMenus() {
|
||||
SetParent(userM).
|
||||
SaveX(ctx)
|
||||
// 列表
|
||||
Global.SysMenu.Create().
|
||||
Global.Debug().SysMenu.Create().
|
||||
SetName("列表").
|
||||
SetLabel("list").
|
||||
SetType(sysmenu.TypeButton).
|
||||
@@ -110,7 +111,7 @@ func generateMenus() {
|
||||
SetOrder(4).
|
||||
SetParent(userM).
|
||||
SaveX(ctx)
|
||||
roleM := Global.SysMenu.Create().
|
||||
roleM := Global.Debug().SysMenu.Create().
|
||||
SetName("角色管理").
|
||||
SetLabel("role").
|
||||
SetType(sysmenu.TypeMenu).
|
||||
@@ -121,7 +122,7 @@ func generateMenus() {
|
||||
SetParent(sysM).
|
||||
SaveX(ctx)
|
||||
// 新增 编辑 删除按钮
|
||||
Global.SysMenu.Create().
|
||||
Global.Debug().SysMenu.Create().
|
||||
SetName("新增").
|
||||
SetLabel("add").
|
||||
SetType(sysmenu.TypeButton).
|
||||
@@ -129,7 +130,7 @@ func generateMenus() {
|
||||
SetOrder(1).
|
||||
SetParent(roleM).
|
||||
SaveX(ctx)
|
||||
Global.SysMenu.Create().
|
||||
Global.Debug().SysMenu.Create().
|
||||
SetName("编辑").
|
||||
SetLabel("edit").
|
||||
SetType(sysmenu.TypeButton).
|
||||
@@ -138,7 +139,7 @@ func generateMenus() {
|
||||
SetParent(roleM).
|
||||
SaveX(ctx)
|
||||
// 删除按钮
|
||||
Global.SysMenu.Create().
|
||||
Global.Debug().SysMenu.Create().
|
||||
SetName("删除").
|
||||
SetLabel("delete").
|
||||
SetType(sysmenu.TypeButton).
|
||||
@@ -146,7 +147,7 @@ func generateMenus() {
|
||||
SetOrder(3).
|
||||
SetParent(roleM).
|
||||
SaveX(ctx)
|
||||
Global.SysMenu.Create().
|
||||
Global.Debug().SysMenu.Create().
|
||||
SetName("列表").
|
||||
SetLabel("list").
|
||||
SetType(sysmenu.TypeButton).
|
||||
@@ -154,7 +155,7 @@ func generateMenus() {
|
||||
SetOrder(4).
|
||||
SetParent(roleM).
|
||||
SaveX(ctx)
|
||||
menuM := Global.SysMenu.Create().
|
||||
menuM := Global.Debug().SysMenu.Create().
|
||||
SetName("菜单管理").
|
||||
SetLabel("menu").
|
||||
SetType(sysmenu.TypeMenu).
|
||||
@@ -165,7 +166,7 @@ func generateMenus() {
|
||||
SetParent(sysM).
|
||||
SaveX(ctx)
|
||||
// 新增 编辑 删除按钮
|
||||
Global.SysMenu.Create().
|
||||
Global.Debug().SysMenu.Create().
|
||||
SetName("新增").
|
||||
SetLabel("add").
|
||||
SetType(sysmenu.TypeButton).
|
||||
@@ -173,7 +174,7 @@ func generateMenus() {
|
||||
SetOrder(1).
|
||||
SetParent(menuM).
|
||||
SaveX(ctx)
|
||||
Global.SysMenu.Create().
|
||||
Global.Debug().SysMenu.Create().
|
||||
SetName("编辑").
|
||||
SetLabel("edit").
|
||||
SetType(sysmenu.TypeButton).
|
||||
@@ -182,7 +183,7 @@ func generateMenus() {
|
||||
SetParent(menuM).
|
||||
SaveX(ctx)
|
||||
// 删除按钮
|
||||
Global.SysMenu.Create().
|
||||
Global.Debug().SysMenu.Create().
|
||||
SetName("删除").
|
||||
SetLabel("delete").
|
||||
SetType(sysmenu.TypeButton).
|
||||
@@ -190,7 +191,7 @@ func generateMenus() {
|
||||
SetOrder(3).
|
||||
SetParent(menuM).
|
||||
SaveX(ctx)
|
||||
Global.SysMenu.Create().
|
||||
Global.Debug().SysMenu.Create().
|
||||
SetName("列表").
|
||||
SetLabel("list").
|
||||
SetType(sysmenu.TypeButton).
|
||||
@@ -203,14 +204,14 @@ func generateMenus() {
|
||||
// 关联角色
|
||||
func relationRoles() {
|
||||
ctx := gctx.New()
|
||||
admin := Global.SysUser.Query().Where(sysuser.UsernameEQ("admin")).OnlyX(ctx)
|
||||
role := Global.SysRole.Query().Where(sysrole.LabelEQ(cst.KeyRoleAdmin)).AllX(ctx)
|
||||
Global.SysUser.UpdateOne(admin).AddRoles(role...).ExecX(ctx)
|
||||
admin := Global.Debug().SysUser.Query().Where(sysuser.UsernameEQ("admin")).OnlyX(ctx)
|
||||
role := Global.Debug().SysRole.Query().Where(sysrole.LabelEQ(cst.KeyRoleAdmin)).AllX(ctx)
|
||||
Global.Debug().SysUser.UpdateOne(admin).AddRoles(role...).ExecX(ctx)
|
||||
}
|
||||
|
||||
// 关联菜单
|
||||
func relationMenus() {
|
||||
ctx := gctx.New()
|
||||
adminRole := Global.SysRole.Query().Where(sysrole.LabelEQ(cst.KeyRoleAdmin)).OnlyX(ctx)
|
||||
Global.SysRole.UpdateOne(adminRole).AddMenus(Global.SysMenu.Query().AllX(ctx)...).ExecX(ctx)
|
||||
adminRole := Global.Debug().SysRole.Query().Where(sysrole.LabelEQ(cst.KeyRoleAdmin)).OnlyX(ctx)
|
||||
Global.Debug().SysRole.UpdateOne(adminRole).AddMenus(Global.Debug().SysMenu.Query().AllX(ctx)...).ExecX(ctx)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"likedream-goframe/internal/common/cst"
|
||||
"likedream-goframe/internal/ent"
|
||||
"likedream-goframe/internal/framework/logger"
|
||||
"likedream-goframe/internal/utils"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var Global *ent.Client
|
||||
|
||||
var GlobalGorm *gorm.DB
|
||||
|
||||
func InitDB() {
|
||||
dbType, err := g.Cfg().Get(gctx.New(), cst.ConfigDatabaseType)
|
||||
utils.OkOrPanic(err)
|
||||
dbDsn, err := g.Cfg().Get(gctx.New(), cst.ConfigDatabaseDsn)
|
||||
utils.OkOrPanic(err)
|
||||
client, err := ent.Open(dbType.String(), dbDsn.String())
|
||||
utils.OkOrPanic(err)
|
||||
// 自动迁移
|
||||
if err := client.Schema.Create(context.Background()); err != nil {
|
||||
logger.Logger.Fatal("failed creating schema resources: %v", zap.Error(err))
|
||||
}
|
||||
Global = client
|
||||
dbDsn2, err := g.Cfg().Get(gctx.New(), cst.ConfigDatabaseDsn2)
|
||||
utils.OkOrPanic(err)
|
||||
GlobalGorm, err = gorm.Open(postgres.Open(dbDsn2.String()), &gorm.Config{})
|
||||
if err != nil {
|
||||
logger.Logger.Fatal("连接pg数据库失败: %v", zap.Error(err))
|
||||
}
|
||||
// 可选:配置连接池
|
||||
sqlDB, err := GlobalGorm.DB()
|
||||
if err != nil {
|
||||
logger.Logger.Fatal("获取数据库实例失败: %v", zap.Error(err))
|
||||
}
|
||||
|
||||
// 设置连接池参数
|
||||
sqlDB.SetMaxIdleConns(10) // 设置空闲连接池中连接的最大数量
|
||||
sqlDB.SetMaxOpenConns(100) // 设置打开数据库连接的最大数量
|
||||
sqlDB.SetConnMaxLifetime(0) // 设置连接可复用的最大时间(0表示永久)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package course
|
||||
|
||||
import (
|
||||
"context"
|
||||
v1 "likedream-goframe/internal/api/v1"
|
||||
"likedream-goframe/internal/application/xuexitong/entity"
|
||||
"likedream-goframe/internal/ent"
|
||||
"likedream-goframe/internal/utils"
|
||||
)
|
||||
|
||||
type IXxtCtrlV1 interface {
|
||||
List(ctx context.Context, req *v1.CourseListReq) (*utils.Response[[]*ent.CourseEntity], error)
|
||||
AddCourse(ctx context.Context, req *v1.AddCourseReq) (*utils.Response[any], error)
|
||||
UpdatePassword(ctx context.Context, req *v1.UpdateCoursePasswordReq) (*utils.Response[any], error)
|
||||
RestartCourse(ctx context.Context, req *v1.RestartCourseReq) (*utils.Response[any], error)
|
||||
PauseCourse(ctx context.Context, req *v1.PauseCourseReq) (*utils.Response[any], error)
|
||||
DeleteCourse(ctx context.Context, req *v1.DeleteCourseReq) (*utils.Response[any], error)
|
||||
QueryCourses(ctx context.Context, req *v1.QueryCourseReq) (*utils.Response[[]entity.XueXiTCourse], error)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package course
|
||||
|
||||
import (
|
||||
"context"
|
||||
v1 "likedream-goframe/internal/api/v1"
|
||||
"likedream-goframe/internal/application/xuexitong/entity"
|
||||
"likedream-goframe/internal/ent"
|
||||
"likedream-goframe/internal/service"
|
||||
"likedream-goframe/internal/utils"
|
||||
)
|
||||
|
||||
type XxtCtrlV1 struct {
|
||||
}
|
||||
|
||||
// List 获取刷课列表
|
||||
func (c XxtCtrlV1) List(ctx context.Context, req *v1.CourseListReq) (res *utils.Response[[]*ent.CourseEntity], err error) {
|
||||
defer utils.Pass(&err)
|
||||
return service.Global.Course.List(ctx, req), nil
|
||||
}
|
||||
|
||||
// AddCourse 添加刷课
|
||||
func (c XxtCtrlV1) AddCourse(ctx context.Context, req *v1.AddCourseReq) (res *utils.Response[any], err error) {
|
||||
defer utils.Pass(&err)
|
||||
return service.Global.Course.AddCourse(ctx, req), nil
|
||||
}
|
||||
|
||||
// UpdatePassword 修改刷课密码
|
||||
func (c XxtCtrlV1) UpdatePassword(ctx context.Context, req *v1.UpdateCoursePasswordReq) (res *utils.Response[any], err error) {
|
||||
defer utils.Pass(&err)
|
||||
return service.Global.Course.UpdatePassword(ctx, req), nil
|
||||
}
|
||||
|
||||
// RestartCourse 重刷课程
|
||||
func (c XxtCtrlV1) RestartCourse(ctx context.Context, req *v1.RestartCourseReq) (res *utils.Response[any], err error) {
|
||||
defer utils.Pass(&err)
|
||||
return service.Global.Course.RestartCourse(ctx, req), nil
|
||||
}
|
||||
|
||||
// PauseCourse 暂停刷课
|
||||
func (c XxtCtrlV1) PauseCourse(ctx context.Context, req *v1.PauseCourseReq) (res *utils.Response[any], err error) {
|
||||
defer utils.Pass(&err)
|
||||
return service.Global.Course.PauseCourse(ctx, req), nil
|
||||
}
|
||||
|
||||
// DeleteCourse 删除刷课
|
||||
func (c XxtCtrlV1) DeleteCourse(ctx context.Context, req *v1.DeleteCourseReq) (res *utils.Response[any], err error) {
|
||||
defer utils.Pass(&err)
|
||||
return service.Global.Course.DeleteCourse(ctx, req), nil
|
||||
}
|
||||
|
||||
// QueryCourses 查询课程列表
|
||||
func (c XxtCtrlV1) QueryCourses(ctx context.Context, req *v1.QueryCourseReq) (res *utils.Response[[]entity.XueXiTCourse], err error) {
|
||||
defer utils.Pass(&err)
|
||||
return service.Global.Course.QueryCourses(ctx, req), nil
|
||||
}
|
||||
|
||||
func NewXxtCtrlV1() IXxtCtrlV1 {
|
||||
return &XxtCtrlV1{}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package cpdaily
|
||||
import (
|
||||
"context"
|
||||
v1 "likedream-goframe/internal/api/v1"
|
||||
"likedream-goframe/internal/application/cpdaily/map_info"
|
||||
"likedream-goframe/internal/ent"
|
||||
"likedream-goframe/internal/service"
|
||||
"likedream-goframe/internal/utils"
|
||||
@@ -11,6 +12,11 @@ import (
|
||||
type CpTaskCtrlV1 struct {
|
||||
}
|
||||
|
||||
func (c CpTaskCtrlV1) FindLocation(ctx context.Context, req *v1.FindLocationReq) (res *utils.Response[any], err error) {
|
||||
defer utils.Pass(&err)
|
||||
return utils.SuccessWithData[any](map_info.SearchAddressLongitudeAndLatitude(ctx, req.Address)), nil
|
||||
}
|
||||
|
||||
func (c CpTaskCtrlV1) List(ctx context.Context, req *v1.CpTaskListReq) (res *utils.Response[[]*ent.CpTask], err error) {
|
||||
defer utils.Pass(&err)
|
||||
return service.Global.CpTask.List(ctx, req), nil
|
||||
@@ -59,6 +65,12 @@ func (c CpTaskCtrlV1) PauseTask(ctx context.Context, req *v1.PauseTaskReq) (res
|
||||
defer utils.Pass(&err)
|
||||
return service.Global.CpTask.PauseTask(ctx, req), nil
|
||||
}
|
||||
|
||||
func (c CpTaskCtrlV1) GetTaskStatistics(ctx context.Context, req *v1.GetTaskStatisticsReq) (res *utils.Response[[]*v1.TaskStatisticsItem], err error) {
|
||||
defer utils.Pass(&err)
|
||||
return service.Global.CpTask.GetTaskStatistics(ctx, req), nil
|
||||
}
|
||||
|
||||
func NewCpTaskCtrlV1() ICpTaskCtrlV1 {
|
||||
return &CpTaskCtrlV1{}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@ package cpdaily
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
v1 "likedream-goframe/internal/api/v1"
|
||||
"likedream-goframe/internal/application/classifier"
|
||||
"likedream-goframe/internal/ent"
|
||||
"likedream-goframe/internal/service"
|
||||
"likedream-goframe/internal/utils"
|
||||
@@ -13,14 +11,6 @@ import (
|
||||
type CpTaskLogCtrlV1 struct {
|
||||
}
|
||||
|
||||
func (c CpTaskLogCtrlV1) OCR(ctx context.Context, req *v1.CpTaskOCRReq) (res *utils.Response[any], err error) {
|
||||
file, _ := req.File.Open()
|
||||
defer file.Close()
|
||||
bytes, _ := io.ReadAll(file)
|
||||
result := classifier.ClassifyImage(bytes, req.Name)
|
||||
return utils.SuccessWithData[any](result), nil
|
||||
}
|
||||
|
||||
func (c CpTaskLogCtrlV1) List(ctx context.Context, req *v1.CpTaskLogListReq) (res *utils.Response[[]*ent.CpTaskLog], err error) {
|
||||
defer utils.Pass(&err)
|
||||
return service.Global.CpTaskLog.List(ctx, req), nil
|
||||
|
||||
@@ -61,3 +61,12 @@ func (c CpUserCtrlV1) AuthorizeRandom(ctx context.Context, req *v1.CpUserAuthori
|
||||
defer utils.Pass(&err)
|
||||
return service.Global.CpUser.AuthorizeRandom(ctx, req), nil
|
||||
}
|
||||
func (c CpUserCtrlV1) LoginGetCode(ctx context.Context, req *v1.LoginGetCode) (res *utils.Response[any], err error) {
|
||||
defer utils.Pass(&err)
|
||||
return service.Global.CpUser.LoginGetCode(ctx, req), nil
|
||||
}
|
||||
|
||||
func (c CpUserCtrlV1) AddByCode(ctx context.Context, req *v1.AddByCode) (res *utils.Response[any], err error) {
|
||||
defer utils.Pass(&err)
|
||||
return service.Global.CpUser.AddByCode(ctx, req), nil
|
||||
}
|
||||
|
||||
@@ -18,4 +18,6 @@ type ICpTaskCtrlV1 interface {
|
||||
Delete(ctx context.Context, req *v1.DeleteTaskReq) (res *utils.Response[any], err error)
|
||||
ExecuteNow(ctx context.Context, req *v1.ExecuteNowReq) (res *utils.Response[any], err error)
|
||||
PauseTask(ctx context.Context, req *v1.PauseTaskReq) (res *utils.Response[any], err error)
|
||||
FindLocation(ctx context.Context, req *v1.FindLocationReq) (res *utils.Response[any], err error)
|
||||
GetTaskStatistics(ctx context.Context, req *v1.GetTaskStatisticsReq) (res *utils.Response[[]*v1.TaskStatisticsItem], err error)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,4 @@ import (
|
||||
|
||||
type ICpTaskLogCtrlV1 interface {
|
||||
List(ctx context.Context, req *v1.CpTaskLogListReq) (res *utils.Response[[]*ent.CpTaskLog], err error)
|
||||
|
||||
OCR(ctx context.Context, req *v1.CpTaskOCRReq) (res *utils.Response[any], err error)
|
||||
}
|
||||
|
||||
@@ -25,4 +25,8 @@ type ICpUserCtrlV1 interface {
|
||||
AuthorizeConfirm(ctx context.Context, req *v1.CpUserAuthorizeConfirmReq) (*utils.Response[any], error)
|
||||
// 随机授权
|
||||
AuthorizeRandom(ctx context.Context, req *v1.CpUserAuthorizeRandomReq) (res *utils.Response[any], err error)
|
||||
|
||||
// 手机登录获取验证码
|
||||
LoginGetCode(ctx context.Context, req *v1.LoginGetCode) (*utils.Response[any], error)
|
||||
AddByCode(ctx context.Context, req *v1.AddByCode) (*utils.Response[any], error)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package music
|
||||
|
||||
import (
|
||||
"context"
|
||||
v1 "likedream-goframe/internal/api/v1"
|
||||
"likedream-goframe/internal/application/music"
|
||||
"likedream-goframe/internal/utils"
|
||||
)
|
||||
|
||||
type IMusicCtrlV1 interface {
|
||||
Search(ctx context.Context, req *v1.SearchMusicReq) (res *utils.Response[[]music.MusicSearchResult], err error)
|
||||
Download(ctx context.Context, req *v1.DownloadMusicReq) (res *utils.Response[any], err error)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package music
|
||||
|
||||
import (
|
||||
"context"
|
||||
v1 "likedream-goframe/internal/api/v1"
|
||||
"likedream-goframe/internal/application/music"
|
||||
"likedream-goframe/internal/utils"
|
||||
)
|
||||
|
||||
type MusicCtrlV1 struct {
|
||||
}
|
||||
|
||||
func NewMusicCtrlV1() IMusicCtrlV1 {
|
||||
return &MusicCtrlV1{}
|
||||
}
|
||||
|
||||
func (c *MusicCtrlV1) Search(ctx context.Context, req *v1.SearchMusicReq) (res *utils.Response[[]music.MusicSearchResult], err error) {
|
||||
defer utils.Pass(&err)
|
||||
return utils.SuccessWithData(music.MusicDownloader.Search(ctx, req.Keyword)), nil
|
||||
}
|
||||
|
||||
func (c *MusicCtrlV1) Download(ctx context.Context, req *v1.DownloadMusicReq) (res *utils.Response[any], err error) {
|
||||
defer utils.Pass(&err)
|
||||
|
||||
// 获取音频下载地址
|
||||
downloadUrl := music.MusicDownloader.Download(ctx, req.Path)
|
||||
return utils.SuccessWithData[any](downloadUrl), nil
|
||||
}
|
||||
@@ -25,4 +25,7 @@ type IUserInfoV1 interface {
|
||||
InviteUserList(ctx context.Context, req *v1.CommonPageReq) (*utils.Response[[]*v1.UserInviteRes], error)
|
||||
// 生成邀请码
|
||||
GenerateInviteCode(ctx context.Context, req *v1.CommonPostReq) (*utils.Response[any], error)
|
||||
|
||||
RequestBalanceMigrate(ctx context.Context, req *v1.RequestBalanceMigrateReq) (*utils.Response[any], error)
|
||||
BalanceMigrate(ctx context.Context, req *v1.BalanceMigrateReq) (*utils.Response[any], error)
|
||||
}
|
||||
|
||||
@@ -13,4 +13,8 @@ type IUserLoginV1 interface {
|
||||
Login(ctx context.Context, req *v1.UserLoginReq) (*utils.Response[any], error)
|
||||
Register(ctx context.Context, req *v1.UserRegisterReq) (*utils.Response[any], error)
|
||||
Confirm(ctx context.Context, req *v1.UserConfirmReq) (*utils.Response[any], error)
|
||||
ResendVerification(ctx context.Context, req *v1.ResendVerificationReq) (*utils.Response[any], error)
|
||||
ForgotPassword(ctx context.Context, req *v1.ForgotPasswordReq) (*utils.Response[any], error)
|
||||
ResetPassword(ctx context.Context, req *v1.ForgetResetPasswordReq) (*utils.Response[any], error)
|
||||
GetResetPasswordInfo(ctx context.Context, req *v1.ResetPasswordInfoReq) (*utils.Response[any], error)
|
||||
}
|
||||
|
||||
@@ -4,8 +4,12 @@ import (
|
||||
"context"
|
||||
v1 "likedream-goframe/internal/api/v1"
|
||||
"likedream-goframe/internal/ent"
|
||||
"likedream-goframe/internal/framework/captcha"
|
||||
"likedream-goframe/internal/framework/redis"
|
||||
"likedream-goframe/internal/service"
|
||||
"likedream-goframe/internal/utils"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
type UserInfoV1 struct {
|
||||
@@ -60,3 +64,34 @@ func (c *UserInfoV1) GenerateInviteCode(ctx context.Context, req *v1.CommonPostR
|
||||
defer utils.Pass(&err)
|
||||
return service.Global.UserInfo.GenerateInviteCode(ctx, req), nil
|
||||
}
|
||||
func (c *UserInfoV1) RequestBalanceMigrate(ctx context.Context, req *v1.RequestBalanceMigrateReq) (res *utils.Response[any], err error) {
|
||||
defer utils.Pass(&err)
|
||||
client := redis.GetClient()
|
||||
dotsStr := client.GetDel(ctx, req.CaptchaKey).Val()
|
||||
if dotsStr == "" {
|
||||
return utils.Fail(10003, "验证码错误"), nil
|
||||
}
|
||||
var dots captcha.ConfirmData
|
||||
err = sonic.Unmarshal([]byte(dotsStr), &dots)
|
||||
utils.OkOrPanic(err)
|
||||
if !captcha.VerifyCaptcha(dots, req.Dots) {
|
||||
return utils.Fail(10003, "验证码错误"), nil
|
||||
}
|
||||
return service.Global.UserInfo.RequestBalanceMigrate(ctx, req), nil
|
||||
}
|
||||
|
||||
func (c *UserInfoV1) BalanceMigrate(ctx context.Context, req *v1.BalanceMigrateReq) (res *utils.Response[any], err error) {
|
||||
defer utils.Pass(&err)
|
||||
client := redis.GetClient()
|
||||
dotsStr := client.GetDel(ctx, req.CaptchaKey).Val()
|
||||
if dotsStr == "" {
|
||||
return utils.Fail(10003, "验证码错误"), nil
|
||||
}
|
||||
var dots captcha.ConfirmData
|
||||
err = sonic.Unmarshal([]byte(dotsStr), &dots)
|
||||
utils.OkOrPanic(err)
|
||||
if !captcha.VerifyCaptcha(dots, req.Dots) {
|
||||
return utils.Fail(10003, "验证码错误"), nil
|
||||
}
|
||||
return service.Global.UserInfo.BalanceMigrate(ctx, req), nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"likedream-goframe/internal/service"
|
||||
"likedream-goframe/internal/utils"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
type UserLoginV1 struct {
|
||||
@@ -39,7 +41,7 @@ func (c *UserLoginV1) Login(ctx context.Context, req *v1.UserLoginReq) (res *uti
|
||||
return utils.Fail(10003, "验证码错误"), nil
|
||||
}
|
||||
var dots captcha.ConfirmData
|
||||
err = json.Unmarshal([]byte(dotsStr), &dots)
|
||||
err = sonic.Unmarshal([]byte(dotsStr), &dots)
|
||||
utils.OkOrPanic(err)
|
||||
if !captcha.VerifyCaptcha(dots, req.Dots) {
|
||||
return utils.Fail(10003, "验证码错误"), nil
|
||||
@@ -49,9 +51,60 @@ func (c *UserLoginV1) Login(ctx context.Context, req *v1.UserLoginReq) (res *uti
|
||||
|
||||
func (c *UserLoginV1) Register(ctx context.Context, req *v1.UserRegisterReq) (res *utils.Response[any], err error) {
|
||||
defer utils.Pass(&err)
|
||||
client := redis.GetClient()
|
||||
dotsStr := client.GetDel(ctx, req.CaptchaKey).Val()
|
||||
if dotsStr == "" {
|
||||
return utils.Fail(10003, "验证码错误"), nil
|
||||
}
|
||||
var dots captcha.ConfirmData
|
||||
err = sonic.Unmarshal([]byte(dotsStr), &dots)
|
||||
utils.OkOrPanic(err)
|
||||
if !captcha.VerifyCaptcha(dots, req.Dots) {
|
||||
return utils.Fail(10003, "验证码错误"), nil
|
||||
}
|
||||
return service.Global.UserLogin.Register(ctx, req), nil
|
||||
}
|
||||
|
||||
func (c *UserLoginV1) Confirm(ctx context.Context, req *v1.UserConfirmReq) (*utils.Response[any], error) {
|
||||
return service.Global.UserLogin.Confirm(ctx, req), nil
|
||||
}
|
||||
|
||||
func (c *UserLoginV1) ResendVerification(ctx context.Context, req *v1.ResendVerificationReq) (res *utils.Response[any], err error) {
|
||||
defer utils.Pass(&err)
|
||||
client := redis.GetClient()
|
||||
dotsStr := client.GetDel(ctx, req.CaptchaKey).Val()
|
||||
if dotsStr == "" {
|
||||
return utils.Fail(10003, "验证码错误"), nil
|
||||
}
|
||||
var dots captcha.ConfirmData
|
||||
err = sonic.Unmarshal([]byte(dotsStr), &dots)
|
||||
utils.OkOrPanic(err)
|
||||
if !captcha.VerifyCaptcha(dots, req.Dots) {
|
||||
return utils.Fail(10003, "验证码错误"), nil
|
||||
}
|
||||
return service.Global.UserLogin.ResendVerification(ctx, req), nil
|
||||
}
|
||||
|
||||
func (c *UserLoginV1) ForgotPassword(ctx context.Context, req *v1.ForgotPasswordReq) (res *utils.Response[any], err error) {
|
||||
defer utils.Pass(&err)
|
||||
client := redis.GetClient()
|
||||
dotsStr := client.GetDel(ctx, req.CaptchaKey).Val()
|
||||
if dotsStr == "" {
|
||||
return utils.Fail(10003, "验证码错误"), nil
|
||||
}
|
||||
var dots captcha.ConfirmData
|
||||
err = sonic.Unmarshal([]byte(dotsStr), &dots)
|
||||
utils.OkOrPanic(err)
|
||||
if !captcha.VerifyCaptcha(dots, req.Dots) {
|
||||
return utils.Fail(10003, "验证码错误"), nil
|
||||
}
|
||||
return service.Global.UserLogin.ForgotPassword(ctx, req), nil
|
||||
}
|
||||
|
||||
func (c *UserLoginV1) ResetPassword(ctx context.Context, req *v1.ForgetResetPasswordReq) (*utils.Response[any], error) {
|
||||
return service.Global.UserLogin.ResetPassword(ctx, req), nil
|
||||
}
|
||||
|
||||
func (c *UserLoginV1) GetResetPasswordInfo(ctx context.Context, req *v1.ResetPasswordInfoReq) (*utils.Response[any], error) {
|
||||
return service.Global.UserLogin.GetResetPasswordInfo(ctx, req), nil
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package cst
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
ConfigOcrServerUrl = "ocr.url"
|
||||
ConfigEnvMode = "env.mode"
|
||||
ConfigDatabaseType = "database.type"
|
||||
ConfigDatabaseDsn = "database.dsn"
|
||||
ConfigServerPort = "server.port"
|
||||
ConfigEmailConfirmBaseUrl = "server.emailConfirmBaseUrl"
|
||||
ConfigAuthorizationKey = "server.authorization"
|
||||
ConfigJwtKey = "server.jwt"
|
||||
ConfigRuoliCaptchaBaseUrl = "ruoliCaptcha.baseUrl"
|
||||
ConfigS3AccessKey = "s3.accessKey"
|
||||
ConfigS3SecretKey = "s3.secretKey"
|
||||
ConfigS3BaseUrl = "s3.baseUrl"
|
||||
ConfigS3Bucket = "s3.bucket"
|
||||
ConfigCpdailyAppVersion = "cpdaily.appVersion"
|
||||
ConfigCpdailySystemVersion = "cpdaily.systemVersion"
|
||||
ConfigCpdailyCalVersion = "cpdaily.calVersion"
|
||||
|
||||
ConfigRedisUrl = "redis.url"
|
||||
|
||||
ConfigTaskConcurrency = "task.concurrency"
|
||||
|
||||
// Email配置常量
|
||||
ConfigEmailSMTPHost = "email.smtpHost"
|
||||
ConfigEmailSMTPPort = "email.smtpPort"
|
||||
ConfigEmailSMTPUsername = "email.smtpUsername"
|
||||
ConfigEmailSMTPPassword = "email.smtpPassword"
|
||||
ConfigEmailFromEmail = "email.fromEmail"
|
||||
ConfigEmailFromName = "email.fromName"
|
||||
)
|
||||
|
||||
const (
|
||||
// 应用程序常量
|
||||
AppName = "今日校园系统"
|
||||
AppCopyright = "© 2024 今日校园系统. 保留所有权利."
|
||||
)
|
||||
|
||||
var (
|
||||
TimeZoneShangHai, _ = time.LoadLocation("Asia/Shanghai")
|
||||
)
|
||||
@@ -1,15 +0,0 @@
|
||||
package cst
|
||||
|
||||
const (
|
||||
RedisUserInfo = "userinfo:"
|
||||
RedisVerifyCode = "verifycode:"
|
||||
RedisCpUserLogin = "cpuserlogin:"
|
||||
RedisCpUserAuthorize = "cpuser:authorize:"
|
||||
)
|
||||
|
||||
var (
|
||||
RedisUserInfoExpire = int64(86400)
|
||||
RedisVerifyCodeExpire = int64(86400)
|
||||
RedisCpUserLoginExpire = int64(180)
|
||||
RedisCpUserAuthorizeExpire = int64(1800)
|
||||
)
|
||||
@@ -1,9 +0,0 @@
|
||||
package cst
|
||||
|
||||
const (
|
||||
TaskCpdaily = "task:cpdaily"
|
||||
TaskEmail = "task:email"
|
||||
|
||||
TaskQueueCpdaily = "queue:cpdaily"
|
||||
TaskQueueEmail = "queue:email"
|
||||
)
|
||||
@@ -1,30 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"likedream-goframe/internal/cst"
|
||||
"likedream-goframe/internal/ent"
|
||||
"likedream-goframe/internal/framework/logger"
|
||||
"likedream-goframe/internal/utils"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var Global *ent.Client
|
||||
|
||||
func InitDB() {
|
||||
dbType, err := g.Cfg().Get(gctx.New(), cst.ConfigDatabaseType)
|
||||
utils.OkOrPanic(err)
|
||||
dbDsn, err := g.Cfg().Get(gctx.New(), cst.ConfigDatabaseDsn)
|
||||
utils.OkOrPanic(err)
|
||||
client, err := ent.Open(dbType.String(), dbDsn.String())
|
||||
utils.OkOrPanic(err)
|
||||
// 自动迁移
|
||||
if err := client.Schema.Create(context.Background()); err != nil {
|
||||
logger.Logger.Fatal("failed creating schema resources: %v", zap.Error(err))
|
||||
}
|
||||
Global = client
|
||||
}
|
||||
+215
-35
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"likedream-goframe/internal/ent/migrate"
|
||||
|
||||
"likedream-goframe/internal/ent/courseentity"
|
||||
"likedream-goframe/internal/ent/cpimage"
|
||||
"likedream-goframe/internal/ent/cpschool"
|
||||
"likedream-goframe/internal/ent/cptask"
|
||||
@@ -37,6 +38,8 @@ type Client struct {
|
||||
config
|
||||
// Schema is the client for creating, migrating and dropping schema.
|
||||
Schema *migrate.Schema
|
||||
// CourseEntity is the client for interacting with the CourseEntity builders.
|
||||
CourseEntity *CourseEntityClient
|
||||
// CpImage is the client for interacting with the CpImage builders.
|
||||
CpImage *CpImageClient
|
||||
// CpSchool is the client for interacting with the CpSchool builders.
|
||||
@@ -70,6 +73,7 @@ func NewClient(opts ...Option) *Client {
|
||||
|
||||
func (c *Client) init() {
|
||||
c.Schema = migrate.NewSchema(c.driver)
|
||||
c.CourseEntity = NewCourseEntityClient(c.config)
|
||||
c.CpImage = NewCpImageClient(c.config)
|
||||
c.CpSchool = NewCpSchoolClient(c.config)
|
||||
c.CpTask = NewCpTaskClient(c.config)
|
||||
@@ -173,19 +177,20 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
|
||||
cfg := c.config
|
||||
cfg.driver = tx
|
||||
return &Tx{
|
||||
ctx: ctx,
|
||||
config: cfg,
|
||||
CpImage: NewCpImageClient(cfg),
|
||||
CpSchool: NewCpSchoolClient(cfg),
|
||||
CpTask: NewCpTaskClient(cfg),
|
||||
CpTaskLog: NewCpTaskLogClient(cfg),
|
||||
CpUser: NewCpUserClient(cfg),
|
||||
SysAgent: NewSysAgentClient(cfg),
|
||||
SysCard: NewSysCardClient(cfg),
|
||||
SysMenu: NewSysMenuClient(cfg),
|
||||
SysRole: NewSysRoleClient(cfg),
|
||||
SysUser: NewSysUserClient(cfg),
|
||||
SysUserLog: NewSysUserLogClient(cfg),
|
||||
ctx: ctx,
|
||||
config: cfg,
|
||||
CourseEntity: NewCourseEntityClient(cfg),
|
||||
CpImage: NewCpImageClient(cfg),
|
||||
CpSchool: NewCpSchoolClient(cfg),
|
||||
CpTask: NewCpTaskClient(cfg),
|
||||
CpTaskLog: NewCpTaskLogClient(cfg),
|
||||
CpUser: NewCpUserClient(cfg),
|
||||
SysAgent: NewSysAgentClient(cfg),
|
||||
SysCard: NewSysCardClient(cfg),
|
||||
SysMenu: NewSysMenuClient(cfg),
|
||||
SysRole: NewSysRoleClient(cfg),
|
||||
SysUser: NewSysUserClient(cfg),
|
||||
SysUserLog: NewSysUserLogClient(cfg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -203,26 +208,27 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
|
||||
cfg := c.config
|
||||
cfg.driver = &txDriver{tx: tx, drv: c.driver}
|
||||
return &Tx{
|
||||
ctx: ctx,
|
||||
config: cfg,
|
||||
CpImage: NewCpImageClient(cfg),
|
||||
CpSchool: NewCpSchoolClient(cfg),
|
||||
CpTask: NewCpTaskClient(cfg),
|
||||
CpTaskLog: NewCpTaskLogClient(cfg),
|
||||
CpUser: NewCpUserClient(cfg),
|
||||
SysAgent: NewSysAgentClient(cfg),
|
||||
SysCard: NewSysCardClient(cfg),
|
||||
SysMenu: NewSysMenuClient(cfg),
|
||||
SysRole: NewSysRoleClient(cfg),
|
||||
SysUser: NewSysUserClient(cfg),
|
||||
SysUserLog: NewSysUserLogClient(cfg),
|
||||
ctx: ctx,
|
||||
config: cfg,
|
||||
CourseEntity: NewCourseEntityClient(cfg),
|
||||
CpImage: NewCpImageClient(cfg),
|
||||
CpSchool: NewCpSchoolClient(cfg),
|
||||
CpTask: NewCpTaskClient(cfg),
|
||||
CpTaskLog: NewCpTaskLogClient(cfg),
|
||||
CpUser: NewCpUserClient(cfg),
|
||||
SysAgent: NewSysAgentClient(cfg),
|
||||
SysCard: NewSysCardClient(cfg),
|
||||
SysMenu: NewSysMenuClient(cfg),
|
||||
SysRole: NewSysRoleClient(cfg),
|
||||
SysUser: NewSysUserClient(cfg),
|
||||
SysUserLog: NewSysUserLogClient(cfg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Debug returns a new debug-client. It's used to get verbose logging on specific operations.
|
||||
//
|
||||
// client.Debug().
|
||||
// CpImage.
|
||||
// CourseEntity.
|
||||
// Query().
|
||||
// Count(ctx)
|
||||
func (c *Client) Debug() *Client {
|
||||
@@ -245,8 +251,8 @@ func (c *Client) Close() error {
|
||||
// In order to add hooks to a specific client, call: `client.Node.Use(...)`.
|
||||
func (c *Client) Use(hooks ...Hook) {
|
||||
for _, n := range []interface{ Use(...Hook) }{
|
||||
c.CpImage, c.CpSchool, c.CpTask, c.CpTaskLog, c.CpUser, c.SysAgent, c.SysCard,
|
||||
c.SysMenu, c.SysRole, c.SysUser, c.SysUserLog,
|
||||
c.CourseEntity, c.CpImage, c.CpSchool, c.CpTask, c.CpTaskLog, c.CpUser,
|
||||
c.SysAgent, c.SysCard, c.SysMenu, c.SysRole, c.SysUser, c.SysUserLog,
|
||||
} {
|
||||
n.Use(hooks...)
|
||||
}
|
||||
@@ -256,8 +262,8 @@ func (c *Client) Use(hooks ...Hook) {
|
||||
// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`.
|
||||
func (c *Client) Intercept(interceptors ...Interceptor) {
|
||||
for _, n := range []interface{ Intercept(...Interceptor) }{
|
||||
c.CpImage, c.CpSchool, c.CpTask, c.CpTaskLog, c.CpUser, c.SysAgent, c.SysCard,
|
||||
c.SysMenu, c.SysRole, c.SysUser, c.SysUserLog,
|
||||
c.CourseEntity, c.CpImage, c.CpSchool, c.CpTask, c.CpTaskLog, c.CpUser,
|
||||
c.SysAgent, c.SysCard, c.SysMenu, c.SysRole, c.SysUser, c.SysUserLog,
|
||||
} {
|
||||
n.Intercept(interceptors...)
|
||||
}
|
||||
@@ -266,6 +272,8 @@ func (c *Client) Intercept(interceptors ...Interceptor) {
|
||||
// Mutate implements the ent.Mutator interface.
|
||||
func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
|
||||
switch m := m.(type) {
|
||||
case *CourseEntityMutation:
|
||||
return c.CourseEntity.mutate(ctx, m)
|
||||
case *CpImageMutation:
|
||||
return c.CpImage.mutate(ctx, m)
|
||||
case *CpSchoolMutation:
|
||||
@@ -293,6 +301,159 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// CourseEntityClient is a client for the CourseEntity schema.
|
||||
type CourseEntityClient struct {
|
||||
config
|
||||
}
|
||||
|
||||
// NewCourseEntityClient returns a client for the CourseEntity from the given config.
|
||||
func NewCourseEntityClient(c config) *CourseEntityClient {
|
||||
return &CourseEntityClient{config: c}
|
||||
}
|
||||
|
||||
// Use adds a list of mutation hooks to the hooks stack.
|
||||
// A call to `Use(f, g, h)` equals to `courseentity.Hooks(f(g(h())))`.
|
||||
func (c *CourseEntityClient) Use(hooks ...Hook) {
|
||||
c.hooks.CourseEntity = append(c.hooks.CourseEntity, hooks...)
|
||||
}
|
||||
|
||||
// Intercept adds a list of query interceptors to the interceptors stack.
|
||||
// A call to `Intercept(f, g, h)` equals to `courseentity.Intercept(f(g(h())))`.
|
||||
func (c *CourseEntityClient) Intercept(interceptors ...Interceptor) {
|
||||
c.inters.CourseEntity = append(c.inters.CourseEntity, interceptors...)
|
||||
}
|
||||
|
||||
// Create returns a builder for creating a CourseEntity entity.
|
||||
func (c *CourseEntityClient) Create() *CourseEntityCreate {
|
||||
mutation := newCourseEntityMutation(c.config, OpCreate)
|
||||
return &CourseEntityCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// CreateBulk returns a builder for creating a bulk of CourseEntity entities.
|
||||
func (c *CourseEntityClient) CreateBulk(builders ...*CourseEntityCreate) *CourseEntityCreateBulk {
|
||||
return &CourseEntityCreateBulk{config: c.config, builders: builders}
|
||||
}
|
||||
|
||||
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
|
||||
// a builder and applies setFunc on it.
|
||||
func (c *CourseEntityClient) MapCreateBulk(slice any, setFunc func(*CourseEntityCreate, int)) *CourseEntityCreateBulk {
|
||||
rv := reflect.ValueOf(slice)
|
||||
if rv.Kind() != reflect.Slice {
|
||||
return &CourseEntityCreateBulk{err: fmt.Errorf("calling to CourseEntityClient.MapCreateBulk with wrong type %T, need slice", slice)}
|
||||
}
|
||||
builders := make([]*CourseEntityCreate, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
builders[i] = c.Create()
|
||||
setFunc(builders[i], i)
|
||||
}
|
||||
return &CourseEntityCreateBulk{config: c.config, builders: builders}
|
||||
}
|
||||
|
||||
// Update returns an update builder for CourseEntity.
|
||||
func (c *CourseEntityClient) Update() *CourseEntityUpdate {
|
||||
mutation := newCourseEntityMutation(c.config, OpUpdate)
|
||||
return &CourseEntityUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// UpdateOne returns an update builder for the given entity.
|
||||
func (c *CourseEntityClient) UpdateOne(_m *CourseEntity) *CourseEntityUpdateOne {
|
||||
mutation := newCourseEntityMutation(c.config, OpUpdateOne, withCourseEntity(_m))
|
||||
return &CourseEntityUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// UpdateOneID returns an update builder for the given id.
|
||||
func (c *CourseEntityClient) UpdateOneID(id int64) *CourseEntityUpdateOne {
|
||||
mutation := newCourseEntityMutation(c.config, OpUpdateOne, withCourseEntityID(id))
|
||||
return &CourseEntityUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// Delete returns a delete builder for CourseEntity.
|
||||
func (c *CourseEntityClient) Delete() *CourseEntityDelete {
|
||||
mutation := newCourseEntityMutation(c.config, OpDelete)
|
||||
return &CourseEntityDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// DeleteOne returns a builder for deleting the given entity.
|
||||
func (c *CourseEntityClient) DeleteOne(_m *CourseEntity) *CourseEntityDeleteOne {
|
||||
return c.DeleteOneID(_m.ID)
|
||||
}
|
||||
|
||||
// DeleteOneID returns a builder for deleting the given entity by its id.
|
||||
func (c *CourseEntityClient) DeleteOneID(id int64) *CourseEntityDeleteOne {
|
||||
builder := c.Delete().Where(courseentity.ID(id))
|
||||
builder.mutation.id = &id
|
||||
builder.mutation.op = OpDeleteOne
|
||||
return &CourseEntityDeleteOne{builder}
|
||||
}
|
||||
|
||||
// Query returns a query builder for CourseEntity.
|
||||
func (c *CourseEntityClient) Query() *CourseEntityQuery {
|
||||
return &CourseEntityQuery{
|
||||
config: c.config,
|
||||
ctx: &QueryContext{Type: TypeCourseEntity},
|
||||
inters: c.Interceptors(),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a CourseEntity entity by its id.
|
||||
func (c *CourseEntityClient) Get(ctx context.Context, id int64) (*CourseEntity, error) {
|
||||
return c.Query().Where(courseentity.ID(id)).Only(ctx)
|
||||
}
|
||||
|
||||
// GetX is like Get, but panics if an error occurs.
|
||||
func (c *CourseEntityClient) GetX(ctx context.Context, id int64) *CourseEntity {
|
||||
obj, err := c.Get(ctx, id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// QuerySysUser queries the sys_user edge of a CourseEntity.
|
||||
func (c *CourseEntityClient) QuerySysUser(_m *CourseEntity) *SysUserQuery {
|
||||
query := (&SysUserClient{config: c.config}).Query()
|
||||
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
|
||||
id := _m.ID
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(courseentity.Table, courseentity.FieldID, id),
|
||||
sqlgraph.To(sysuser.Table, sysuser.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, courseentity.SysUserTable, courseentity.SysUserColumn),
|
||||
)
|
||||
schemaConfig := _m.schemaConfig
|
||||
step.To.Schema = schemaConfig.SysUser
|
||||
step.Edge.Schema = schemaConfig.CourseEntity
|
||||
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||
return fromV, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// Hooks returns the client hooks.
|
||||
func (c *CourseEntityClient) Hooks() []Hook {
|
||||
hooks := c.hooks.CourseEntity
|
||||
return append(hooks[:len(hooks):len(hooks)], courseentity.Hooks[:]...)
|
||||
}
|
||||
|
||||
// Interceptors returns the client interceptors.
|
||||
func (c *CourseEntityClient) Interceptors() []Interceptor {
|
||||
return c.inters.CourseEntity
|
||||
}
|
||||
|
||||
func (c *CourseEntityClient) mutate(ctx context.Context, m *CourseEntityMutation) (Value, error) {
|
||||
switch m.Op() {
|
||||
case OpCreate:
|
||||
return (&CourseEntityCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpUpdate:
|
||||
return (&CourseEntityUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpUpdateOne:
|
||||
return (&CourseEntityUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpDelete, OpDeleteOne:
|
||||
return (&CourseEntityDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
|
||||
default:
|
||||
return nil, fmt.Errorf("ent: unknown CourseEntity mutation op: %q", m.Op())
|
||||
}
|
||||
}
|
||||
|
||||
// CpImageClient is a client for the CpImage schema.
|
||||
type CpImageClient struct {
|
||||
config
|
||||
@@ -2139,6 +2300,25 @@ func (c *SysUserClient) QueryCpTask(_m *SysUser) *CpTaskQuery {
|
||||
return query
|
||||
}
|
||||
|
||||
// QueryCourseEntity queries the course_entity edge of a SysUser.
|
||||
func (c *SysUserClient) QueryCourseEntity(_m *SysUser) *CourseEntityQuery {
|
||||
query := (&CourseEntityClient{config: c.config}).Query()
|
||||
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
|
||||
id := _m.ID
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(sysuser.Table, sysuser.FieldID, id),
|
||||
sqlgraph.To(courseentity.Table, courseentity.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, sysuser.CourseEntityTable, sysuser.CourseEntityColumn),
|
||||
)
|
||||
schemaConfig := _m.schemaConfig
|
||||
step.To.Schema = schemaConfig.CourseEntity
|
||||
step.Edge.Schema = schemaConfig.CourseEntity
|
||||
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||
return fromV, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// QueryCpTaskLogs queries the cp_task_logs edge of a SysUser.
|
||||
func (c *SysUserClient) QueryCpTaskLogs(_m *SysUser) *CpTaskLogQuery {
|
||||
query := (&CpTaskLogClient{config: c.config}).Query()
|
||||
@@ -2377,12 +2557,12 @@ func (c *SysUserLogClient) mutate(ctx context.Context, m *SysUserLogMutation) (V
|
||||
// hooks and interceptors per client, for fast access.
|
||||
type (
|
||||
hooks struct {
|
||||
CpImage, CpSchool, CpTask, CpTaskLog, CpUser, SysAgent, SysCard, SysMenu,
|
||||
SysRole, SysUser, SysUserLog []ent.Hook
|
||||
CourseEntity, CpImage, CpSchool, CpTask, CpTaskLog, CpUser, SysAgent, SysCard,
|
||||
SysMenu, SysRole, SysUser, SysUserLog []ent.Hook
|
||||
}
|
||||
inters struct {
|
||||
CpImage, CpSchool, CpTask, CpTaskLog, CpUser, SysAgent, SysCard, SysMenu,
|
||||
SysRole, SysUser, SysUserLog []ent.Interceptor
|
||||
CourseEntity, CpImage, CpSchool, CpTask, CpTaskLog, CpUser, SysAgent, SysCard,
|
||||
SysMenu, SysRole, SysUser, SysUserLog []ent.Interceptor
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
// ENT数据库文件,请勿编辑。
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"likedream-goframe/internal/ent/courseentity"
|
||||
"likedream-goframe/internal/ent/sysuser"
|
||||
"likedream-goframe/internal/framework/ent2"
|
||||
"strings"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect/sql"
|
||||
)
|
||||
|
||||
// CourseEntity is the model entity for the CourseEntity schema.
|
||||
type CourseEntity struct {
|
||||
config `json:"-"`
|
||||
// ID of the ent.
|
||||
ID int64 `json:"id,omitempty"`
|
||||
// CreateTime holds the value of the "create_time" field.
|
||||
CreateTime *ent2.CustomTime `json:"createTime"`
|
||||
// UpdateTime holds the value of the "update_time" field.
|
||||
UpdateTime *ent2.CustomTime `json:"updateTime"`
|
||||
// UserID holds the value of the "user_id" field.
|
||||
UserID int64 `json:"user_id,omitempty"`
|
||||
// Username holds the value of the "username" field.
|
||||
Username string `json:"username,omitempty"`
|
||||
// Password holds the value of the "password" field.
|
||||
Password string `json:"-"`
|
||||
// Type holds the value of the "type" field.
|
||||
Type courseentity.Type `json:"type,omitempty"`
|
||||
// CourseName holds the value of the "course_name" field.
|
||||
CourseName string `json:"course_name,omitempty"`
|
||||
// CourseID holds the value of the "course_id" field.
|
||||
CourseID string `json:"course_id,omitempty"`
|
||||
// Need holds the value of the "need" field.
|
||||
Need courseentity.Need `json:"need,omitempty"`
|
||||
// Status holds the value of the "status" field.
|
||||
Status courseentity.Status `json:"status,omitempty"`
|
||||
// TaskCount holds the value of the "task_count" field.
|
||||
TaskCount int `json:"task_count,omitempty"`
|
||||
// SuccessCount holds the value of the "success_count" field.
|
||||
SuccessCount int `json:"success_count,omitempty"`
|
||||
// ExamState holds the value of the "exam_state" field.
|
||||
ExamState courseentity.ExamState `json:"exam_state,omitempty"`
|
||||
// Violence holds the value of the "violence" field.
|
||||
Violence courseentity.Violence `json:"violence,omitempty"`
|
||||
// Desc holds the value of the "desc" field.
|
||||
Desc string `json:"desc,omitempty"`
|
||||
// Edges holds the relations/edges for other nodes in the graph.
|
||||
// The values are being populated by the CourseEntityQuery when eager-loading is set.
|
||||
Edges CourseEntityEdges `json:"edges"`
|
||||
selectValues sql.SelectValues
|
||||
}
|
||||
|
||||
// CourseEntityEdges holds the relations/edges for other nodes in the graph.
|
||||
type CourseEntityEdges struct {
|
||||
// SysUser holds the value of the sys_user edge.
|
||||
SysUser *SysUser `json:"sys_user,omitempty"`
|
||||
// loadedTypes holds the information for reporting if a
|
||||
// type was loaded (or requested) in eager-loading or not.
|
||||
loadedTypes [1]bool
|
||||
}
|
||||
|
||||
// SysUserOrErr returns the SysUser value or an error if the edge
|
||||
// was not loaded in eager-loading, or loaded but was not found.
|
||||
func (e CourseEntityEdges) SysUserOrErr() (*SysUser, error) {
|
||||
if e.SysUser != nil {
|
||||
return e.SysUser, nil
|
||||
} else if e.loadedTypes[0] {
|
||||
return nil, &NotFoundError{label: sysuser.Label}
|
||||
}
|
||||
return nil, &NotLoadedError{edge: "sys_user"}
|
||||
}
|
||||
|
||||
// scanValues returns the types for scanning values from sql.Rows.
|
||||
func (*CourseEntity) scanValues(columns []string) ([]any, error) {
|
||||
values := make([]any, len(columns))
|
||||
for i := range columns {
|
||||
switch columns[i] {
|
||||
case courseentity.FieldCreateTime, courseentity.FieldUpdateTime:
|
||||
values[i] = new(ent2.CustomTime)
|
||||
case courseentity.FieldID, courseentity.FieldUserID, courseentity.FieldTaskCount, courseentity.FieldSuccessCount:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case courseentity.FieldUsername, courseentity.FieldPassword, courseentity.FieldType, courseentity.FieldCourseName, courseentity.FieldCourseID, courseentity.FieldNeed, courseentity.FieldStatus, courseentity.FieldExamState, courseentity.FieldViolence, courseentity.FieldDesc:
|
||||
values[i] = new(sql.NullString)
|
||||
default:
|
||||
values[i] = new(sql.UnknownType)
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// assignValues assigns the values that were returned from sql.Rows (after scanning)
|
||||
// to the CourseEntity fields.
|
||||
func (_m *CourseEntity) assignValues(columns []string, values []any) error {
|
||||
if m, n := len(values), len(columns); m < n {
|
||||
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
|
||||
}
|
||||
for i := range columns {
|
||||
switch columns[i] {
|
||||
case courseentity.FieldID:
|
||||
value, ok := values[i].(*sql.NullInt64)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field id", value)
|
||||
}
|
||||
_m.ID = int64(value.Int64)
|
||||
case courseentity.FieldCreateTime:
|
||||
if value, ok := values[i].(*ent2.CustomTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field create_time", values[i])
|
||||
} else if value != nil {
|
||||
_m.CreateTime = value
|
||||
}
|
||||
case courseentity.FieldUpdateTime:
|
||||
if value, ok := values[i].(*ent2.CustomTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field update_time", values[i])
|
||||
} else if value != nil {
|
||||
_m.UpdateTime = value
|
||||
}
|
||||
case courseentity.FieldUserID:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field user_id", values[i])
|
||||
} else if value.Valid {
|
||||
_m.UserID = value.Int64
|
||||
}
|
||||
case courseentity.FieldUsername:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field username", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Username = value.String
|
||||
}
|
||||
case courseentity.FieldPassword:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field password", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Password = value.String
|
||||
}
|
||||
case courseentity.FieldType:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field type", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Type = courseentity.Type(value.String)
|
||||
}
|
||||
case courseentity.FieldCourseName:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field course_name", values[i])
|
||||
} else if value.Valid {
|
||||
_m.CourseName = value.String
|
||||
}
|
||||
case courseentity.FieldCourseID:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field course_id", values[i])
|
||||
} else if value.Valid {
|
||||
_m.CourseID = value.String
|
||||
}
|
||||
case courseentity.FieldNeed:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field need", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Need = courseentity.Need(value.String)
|
||||
}
|
||||
case courseentity.FieldStatus:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field status", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Status = courseentity.Status(value.String)
|
||||
}
|
||||
case courseentity.FieldTaskCount:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field task_count", values[i])
|
||||
} else if value.Valid {
|
||||
_m.TaskCount = int(value.Int64)
|
||||
}
|
||||
case courseentity.FieldSuccessCount:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field success_count", values[i])
|
||||
} else if value.Valid {
|
||||
_m.SuccessCount = int(value.Int64)
|
||||
}
|
||||
case courseentity.FieldExamState:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field exam_state", values[i])
|
||||
} else if value.Valid {
|
||||
_m.ExamState = courseentity.ExamState(value.String)
|
||||
}
|
||||
case courseentity.FieldViolence:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field violence", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Violence = courseentity.Violence(value.String)
|
||||
}
|
||||
case courseentity.FieldDesc:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field desc", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Desc = value.String
|
||||
}
|
||||
default:
|
||||
_m.selectValues.Set(columns[i], values[i])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value returns the ent.Value that was dynamically selected and assigned to the CourseEntity.
|
||||
// This includes values selected through modifiers, order, etc.
|
||||
func (_m *CourseEntity) Value(name string) (ent.Value, error) {
|
||||
return _m.selectValues.Get(name)
|
||||
}
|
||||
|
||||
// QuerySysUser queries the "sys_user" edge of the CourseEntity entity.
|
||||
func (_m *CourseEntity) QuerySysUser() *SysUserQuery {
|
||||
return NewCourseEntityClient(_m.config).QuerySysUser(_m)
|
||||
}
|
||||
|
||||
// Update returns a builder for updating this CourseEntity.
|
||||
// Note that you need to call CourseEntity.Unwrap() before calling this method if this CourseEntity
|
||||
// was returned from a transaction, and the transaction was committed or rolled back.
|
||||
func (_m *CourseEntity) Update() *CourseEntityUpdateOne {
|
||||
return NewCourseEntityClient(_m.config).UpdateOne(_m)
|
||||
}
|
||||
|
||||
// Unwrap unwraps the CourseEntity entity that was returned from a transaction after it was closed,
|
||||
// so that all future queries will be executed through the driver which created the transaction.
|
||||
func (_m *CourseEntity) Unwrap() *CourseEntity {
|
||||
_tx, ok := _m.config.driver.(*txDriver)
|
||||
if !ok {
|
||||
panic("ent: CourseEntity is not a transactional entity")
|
||||
}
|
||||
_m.config.driver = _tx.drv
|
||||
return _m
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer.
|
||||
func (_m *CourseEntity) String() string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString("CourseEntity(")
|
||||
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
|
||||
builder.WriteString("create_time=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.CreateTime))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("update_time=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.UpdateTime))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("user_id=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.UserID))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("username=")
|
||||
builder.WriteString(_m.Username)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("password=<sensitive>")
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("type=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.Type))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("course_name=")
|
||||
builder.WriteString(_m.CourseName)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("course_id=")
|
||||
builder.WriteString(_m.CourseID)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("need=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.Need))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("status=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.Status))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("task_count=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.TaskCount))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("success_count=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.SuccessCount))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("exam_state=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.ExamState))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("violence=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.Violence))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("desc=")
|
||||
builder.WriteString(_m.Desc)
|
||||
builder.WriteByte(')')
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// CourseEntities is a parsable slice of CourseEntity.
|
||||
type CourseEntities []*CourseEntity
|
||||
@@ -0,0 +1,335 @@
|
||||
// ENT数据库文件,请勿编辑。
|
||||
|
||||
package courseentity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"likedream-goframe/internal/framework/ent2"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
)
|
||||
|
||||
const (
|
||||
// Label holds the string label denoting the courseentity type in the database.
|
||||
Label = "course_entity"
|
||||
// FieldID holds the string denoting the id field in the database.
|
||||
FieldID = "id"
|
||||
// FieldCreateTime holds the string denoting the create_time field in the database.
|
||||
FieldCreateTime = "create_time"
|
||||
// FieldUpdateTime holds the string denoting the update_time field in the database.
|
||||
FieldUpdateTime = "update_time"
|
||||
// FieldUserID holds the string denoting the user_id field in the database.
|
||||
FieldUserID = "user_id"
|
||||
// FieldUsername holds the string denoting the username field in the database.
|
||||
FieldUsername = "username"
|
||||
// FieldPassword holds the string denoting the password field in the database.
|
||||
FieldPassword = "password"
|
||||
// FieldType holds the string denoting the type field in the database.
|
||||
FieldType = "type"
|
||||
// FieldCourseName holds the string denoting the course_name field in the database.
|
||||
FieldCourseName = "course_name"
|
||||
// FieldCourseID holds the string denoting the course_id field in the database.
|
||||
FieldCourseID = "course_id"
|
||||
// FieldNeed holds the string denoting the need field in the database.
|
||||
FieldNeed = "need"
|
||||
// FieldStatus holds the string denoting the status field in the database.
|
||||
FieldStatus = "status"
|
||||
// FieldTaskCount holds the string denoting the task_count field in the database.
|
||||
FieldTaskCount = "task_count"
|
||||
// FieldSuccessCount holds the string denoting the success_count field in the database.
|
||||
FieldSuccessCount = "success_count"
|
||||
// FieldExamState holds the string denoting the exam_state field in the database.
|
||||
FieldExamState = "exam_state"
|
||||
// FieldViolence holds the string denoting the violence field in the database.
|
||||
FieldViolence = "violence"
|
||||
// FieldDesc holds the string denoting the desc field in the database.
|
||||
FieldDesc = "desc"
|
||||
// EdgeSysUser holds the string denoting the sys_user edge name in mutations.
|
||||
EdgeSysUser = "sys_user"
|
||||
// Table holds the table name of the courseentity in the database.
|
||||
Table = "course_entity"
|
||||
// SysUserTable is the table that holds the sys_user relation/edge.
|
||||
SysUserTable = "course_entity"
|
||||
// SysUserInverseTable is the table name for the SysUser entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "sysuser" package.
|
||||
SysUserInverseTable = "sys_user"
|
||||
// SysUserColumn is the table column denoting the sys_user relation/edge.
|
||||
SysUserColumn = "user_id"
|
||||
)
|
||||
|
||||
// Columns holds all SQL columns for courseentity fields.
|
||||
var Columns = []string{
|
||||
FieldID,
|
||||
FieldCreateTime,
|
||||
FieldUpdateTime,
|
||||
FieldUserID,
|
||||
FieldUsername,
|
||||
FieldPassword,
|
||||
FieldType,
|
||||
FieldCourseName,
|
||||
FieldCourseID,
|
||||
FieldNeed,
|
||||
FieldStatus,
|
||||
FieldTaskCount,
|
||||
FieldSuccessCount,
|
||||
FieldExamState,
|
||||
FieldViolence,
|
||||
FieldDesc,
|
||||
}
|
||||
|
||||
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||
func ValidColumn(column string) bool {
|
||||
for i := range Columns {
|
||||
if column == Columns[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Note that the variables below are initialized by the runtime
|
||||
// package on the initialization of the application. Therefore,
|
||||
// it should be imported in the main as follows:
|
||||
//
|
||||
// import _ "likedream-goframe/internal/ent/runtime"
|
||||
var (
|
||||
Hooks [1]ent.Hook
|
||||
// DefaultCreateTime holds the default value on creation for the "create_time" field.
|
||||
DefaultCreateTime func() *ent2.CustomTime
|
||||
// DefaultUpdateTime holds the default value on creation for the "update_time" field.
|
||||
DefaultUpdateTime func() *ent2.CustomTime
|
||||
// DefaultTaskCount holds the default value on creation for the "task_count" field.
|
||||
DefaultTaskCount int
|
||||
// DefaultSuccessCount holds the default value on creation for the "success_count" field.
|
||||
DefaultSuccessCount int
|
||||
)
|
||||
|
||||
// Type defines the type for the "type" enum field.
|
||||
type Type string
|
||||
|
||||
// Type values.
|
||||
const (
|
||||
TypeXxt Type = "xxt"
|
||||
)
|
||||
|
||||
func (_type Type) String() string {
|
||||
return string(_type)
|
||||
}
|
||||
|
||||
// TypeValidator is a validator for the "type" field enum values. It is called by the builders before save.
|
||||
func TypeValidator(_type Type) error {
|
||||
switch _type {
|
||||
case TypeXxt:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("courseentity: invalid enum value for type field: %q", _type)
|
||||
}
|
||||
}
|
||||
|
||||
// Need defines the type for the "need" enum field.
|
||||
type Need string
|
||||
|
||||
// Need values.
|
||||
const (
|
||||
NeedTask Need = "task"
|
||||
NeedExam Need = "exam"
|
||||
NeedAll Need = "all"
|
||||
)
|
||||
|
||||
func (n Need) String() string {
|
||||
return string(n)
|
||||
}
|
||||
|
||||
// NeedValidator is a validator for the "need" field enum values. It is called by the builders before save.
|
||||
func NeedValidator(n Need) error {
|
||||
switch n {
|
||||
case NeedTask, NeedExam, NeedAll:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("courseentity: invalid enum value for need field: %q", n)
|
||||
}
|
||||
}
|
||||
|
||||
// Status defines the type for the "status" enum field.
|
||||
type Status string
|
||||
|
||||
// StatusNotStart is the default value of the Status enum.
|
||||
const DefaultStatus = StatusNotStart
|
||||
|
||||
// Status values.
|
||||
const (
|
||||
StatusNotStart Status = "not_start"
|
||||
StatusWaiting Status = "waiting"
|
||||
StatusDoing Status = "doing"
|
||||
StatusDone Status = "done"
|
||||
StatusFail Status = "fail"
|
||||
StatusPause Status = "pause"
|
||||
)
|
||||
|
||||
func (s Status) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// StatusValidator is a validator for the "status" field enum values. It is called by the builders before save.
|
||||
func StatusValidator(s Status) error {
|
||||
switch s {
|
||||
case StatusNotStart, StatusWaiting, StatusDoing, StatusDone, StatusFail, StatusPause:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("courseentity: invalid enum value for status field: %q", s)
|
||||
}
|
||||
}
|
||||
|
||||
// ExamState defines the type for the "exam_state" enum field.
|
||||
type ExamState string
|
||||
|
||||
// ExamStateNotStart is the default value of the ExamState enum.
|
||||
const DefaultExamState = ExamStateNotStart
|
||||
|
||||
// ExamState values.
|
||||
const (
|
||||
ExamStateNotStart ExamState = "not_start"
|
||||
ExamStateWaiting ExamState = "waiting"
|
||||
ExamStateDoing ExamState = "doing"
|
||||
ExamStateDone ExamState = "done"
|
||||
ExamStateFail ExamState = "fail"
|
||||
)
|
||||
|
||||
func (es ExamState) String() string {
|
||||
return string(es)
|
||||
}
|
||||
|
||||
// ExamStateValidator is a validator for the "exam_state" field enum values. It is called by the builders before save.
|
||||
func ExamStateValidator(es ExamState) error {
|
||||
switch es {
|
||||
case ExamStateNotStart, ExamStateWaiting, ExamStateDoing, ExamStateDone, ExamStateFail:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("courseentity: invalid enum value for exam_state field: %q", es)
|
||||
}
|
||||
}
|
||||
|
||||
// Violence defines the type for the "violence" enum field.
|
||||
type Violence string
|
||||
|
||||
// ViolenceNo is the default value of the Violence enum.
|
||||
const DefaultViolence = ViolenceNo
|
||||
|
||||
// Violence values.
|
||||
const (
|
||||
ViolenceNo Violence = "no"
|
||||
ViolenceYes Violence = "yes"
|
||||
)
|
||||
|
||||
func (v Violence) String() string {
|
||||
return string(v)
|
||||
}
|
||||
|
||||
// ViolenceValidator is a validator for the "violence" field enum values. It is called by the builders before save.
|
||||
func ViolenceValidator(v Violence) error {
|
||||
switch v {
|
||||
case ViolenceNo, ViolenceYes:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("courseentity: invalid enum value for violence field: %q", v)
|
||||
}
|
||||
}
|
||||
|
||||
// OrderOption defines the ordering options for the CourseEntity queries.
|
||||
type OrderOption func(*sql.Selector)
|
||||
|
||||
// ByID orders the results by the id field.
|
||||
func ByID(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldID, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByCreateTime orders the results by the create_time field.
|
||||
func ByCreateTime(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldCreateTime, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByUpdateTime orders the results by the update_time field.
|
||||
func ByUpdateTime(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldUpdateTime, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByUserID orders the results by the user_id field.
|
||||
func ByUserID(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldUserID, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByUsername orders the results by the username field.
|
||||
func ByUsername(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldUsername, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByPassword orders the results by the password field.
|
||||
func ByPassword(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldPassword, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByType orders the results by the type field.
|
||||
func ByType(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldType, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByCourseName orders the results by the course_name field.
|
||||
func ByCourseName(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldCourseName, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByCourseID orders the results by the course_id field.
|
||||
func ByCourseID(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldCourseID, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByNeed orders the results by the need field.
|
||||
func ByNeed(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldNeed, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByStatus orders the results by the status field.
|
||||
func ByStatus(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldStatus, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByTaskCount orders the results by the task_count field.
|
||||
func ByTaskCount(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldTaskCount, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// BySuccessCount orders the results by the success_count field.
|
||||
func BySuccessCount(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldSuccessCount, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByExamState orders the results by the exam_state field.
|
||||
func ByExamState(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldExamState, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByViolence orders the results by the violence field.
|
||||
func ByViolence(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldViolence, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByDesc orders the results by the desc field.
|
||||
func ByDesc(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldDesc, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// BySysUserField orders the results by sys_user field.
|
||||
func BySysUserField(field string, opts ...sql.OrderTermOption) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
sqlgraph.OrderByNeighborTerms(s, newSysUserStep(), sql.OrderByField(field, opts...))
|
||||
}
|
||||
}
|
||||
func newSysUserStep() *sqlgraph.Step {
|
||||
return sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.To(SysUserInverseTable, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, SysUserTable, SysUserColumn),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,826 @@
|
||||
// ENT数据库文件,请勿编辑。
|
||||
|
||||
package courseentity
|
||||
|
||||
import (
|
||||
"likedream-goframe/internal/ent/internal"
|
||||
"likedream-goframe/internal/ent/predicate"
|
||||
"likedream-goframe/internal/framework/ent2"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
)
|
||||
|
||||
// ID filters vertices based on their ID field.
|
||||
func ID(id int64) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDEQ applies the EQ predicate on the ID field.
|
||||
func IDEQ(id int64) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDNEQ applies the NEQ predicate on the ID field.
|
||||
func IDNEQ(id int64) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDIn applies the In predicate on the ID field.
|
||||
func IDIn(ids ...int64) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIn(FieldID, ids...))
|
||||
}
|
||||
|
||||
// IDNotIn applies the NotIn predicate on the ID field.
|
||||
func IDNotIn(ids ...int64) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotIn(FieldID, ids...))
|
||||
}
|
||||
|
||||
// IDGT applies the GT predicate on the ID field.
|
||||
func IDGT(id int64) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGT(FieldID, id))
|
||||
}
|
||||
|
||||
// IDGTE applies the GTE predicate on the ID field.
|
||||
func IDGTE(id int64) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGTE(FieldID, id))
|
||||
}
|
||||
|
||||
// IDLT applies the LT predicate on the ID field.
|
||||
func IDLT(id int64) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLT(FieldID, id))
|
||||
}
|
||||
|
||||
// IDLTE applies the LTE predicate on the ID field.
|
||||
func IDLTE(id int64) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLTE(FieldID, id))
|
||||
}
|
||||
|
||||
// CreateTime applies equality check predicate on the "create_time" field. It's identical to CreateTimeEQ.
|
||||
func CreateTime(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldCreateTime, v))
|
||||
}
|
||||
|
||||
// UpdateTime applies equality check predicate on the "update_time" field. It's identical to UpdateTimeEQ.
|
||||
func UpdateTime(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldUpdateTime, v))
|
||||
}
|
||||
|
||||
// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ.
|
||||
func UserID(v int64) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldUserID, v))
|
||||
}
|
||||
|
||||
// Username applies equality check predicate on the "username" field. It's identical to UsernameEQ.
|
||||
func Username(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldUsername, v))
|
||||
}
|
||||
|
||||
// Password applies equality check predicate on the "password" field. It's identical to PasswordEQ.
|
||||
func Password(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldPassword, v))
|
||||
}
|
||||
|
||||
// CourseName applies equality check predicate on the "course_name" field. It's identical to CourseNameEQ.
|
||||
func CourseName(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldCourseName, v))
|
||||
}
|
||||
|
||||
// CourseID applies equality check predicate on the "course_id" field. It's identical to CourseIDEQ.
|
||||
func CourseID(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldCourseID, v))
|
||||
}
|
||||
|
||||
// TaskCount applies equality check predicate on the "task_count" field. It's identical to TaskCountEQ.
|
||||
func TaskCount(v int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldTaskCount, v))
|
||||
}
|
||||
|
||||
// SuccessCount applies equality check predicate on the "success_count" field. It's identical to SuccessCountEQ.
|
||||
func SuccessCount(v int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldSuccessCount, v))
|
||||
}
|
||||
|
||||
// Desc applies equality check predicate on the "desc" field. It's identical to DescEQ.
|
||||
func Desc(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldDesc, v))
|
||||
}
|
||||
|
||||
// CreateTimeEQ applies the EQ predicate on the "create_time" field.
|
||||
func CreateTimeEQ(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldCreateTime, v))
|
||||
}
|
||||
|
||||
// CreateTimeNEQ applies the NEQ predicate on the "create_time" field.
|
||||
func CreateTimeNEQ(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNEQ(FieldCreateTime, v))
|
||||
}
|
||||
|
||||
// CreateTimeIn applies the In predicate on the "create_time" field.
|
||||
func CreateTimeIn(vs ...*ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIn(FieldCreateTime, vs...))
|
||||
}
|
||||
|
||||
// CreateTimeNotIn applies the NotIn predicate on the "create_time" field.
|
||||
func CreateTimeNotIn(vs ...*ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotIn(FieldCreateTime, vs...))
|
||||
}
|
||||
|
||||
// CreateTimeGT applies the GT predicate on the "create_time" field.
|
||||
func CreateTimeGT(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGT(FieldCreateTime, v))
|
||||
}
|
||||
|
||||
// CreateTimeGTE applies the GTE predicate on the "create_time" field.
|
||||
func CreateTimeGTE(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGTE(FieldCreateTime, v))
|
||||
}
|
||||
|
||||
// CreateTimeLT applies the LT predicate on the "create_time" field.
|
||||
func CreateTimeLT(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLT(FieldCreateTime, v))
|
||||
}
|
||||
|
||||
// CreateTimeLTE applies the LTE predicate on the "create_time" field.
|
||||
func CreateTimeLTE(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLTE(FieldCreateTime, v))
|
||||
}
|
||||
|
||||
// CreateTimeContains applies the Contains predicate on the "create_time" field.
|
||||
func CreateTimeContains(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
vc := v.String()
|
||||
return predicate.CourseEntity(sql.FieldContains(FieldCreateTime, vc))
|
||||
}
|
||||
|
||||
// CreateTimeHasPrefix applies the HasPrefix predicate on the "create_time" field.
|
||||
func CreateTimeHasPrefix(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
vc := v.String()
|
||||
return predicate.CourseEntity(sql.FieldHasPrefix(FieldCreateTime, vc))
|
||||
}
|
||||
|
||||
// CreateTimeHasSuffix applies the HasSuffix predicate on the "create_time" field.
|
||||
func CreateTimeHasSuffix(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
vc := v.String()
|
||||
return predicate.CourseEntity(sql.FieldHasSuffix(FieldCreateTime, vc))
|
||||
}
|
||||
|
||||
// CreateTimeEqualFold applies the EqualFold predicate on the "create_time" field.
|
||||
func CreateTimeEqualFold(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
vc := v.String()
|
||||
return predicate.CourseEntity(sql.FieldEqualFold(FieldCreateTime, vc))
|
||||
}
|
||||
|
||||
// CreateTimeContainsFold applies the ContainsFold predicate on the "create_time" field.
|
||||
func CreateTimeContainsFold(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
vc := v.String()
|
||||
return predicate.CourseEntity(sql.FieldContainsFold(FieldCreateTime, vc))
|
||||
}
|
||||
|
||||
// UpdateTimeEQ applies the EQ predicate on the "update_time" field.
|
||||
func UpdateTimeEQ(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldUpdateTime, v))
|
||||
}
|
||||
|
||||
// UpdateTimeNEQ applies the NEQ predicate on the "update_time" field.
|
||||
func UpdateTimeNEQ(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNEQ(FieldUpdateTime, v))
|
||||
}
|
||||
|
||||
// UpdateTimeIn applies the In predicate on the "update_time" field.
|
||||
func UpdateTimeIn(vs ...*ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIn(FieldUpdateTime, vs...))
|
||||
}
|
||||
|
||||
// UpdateTimeNotIn applies the NotIn predicate on the "update_time" field.
|
||||
func UpdateTimeNotIn(vs ...*ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotIn(FieldUpdateTime, vs...))
|
||||
}
|
||||
|
||||
// UpdateTimeGT applies the GT predicate on the "update_time" field.
|
||||
func UpdateTimeGT(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGT(FieldUpdateTime, v))
|
||||
}
|
||||
|
||||
// UpdateTimeGTE applies the GTE predicate on the "update_time" field.
|
||||
func UpdateTimeGTE(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGTE(FieldUpdateTime, v))
|
||||
}
|
||||
|
||||
// UpdateTimeLT applies the LT predicate on the "update_time" field.
|
||||
func UpdateTimeLT(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLT(FieldUpdateTime, v))
|
||||
}
|
||||
|
||||
// UpdateTimeLTE applies the LTE predicate on the "update_time" field.
|
||||
func UpdateTimeLTE(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLTE(FieldUpdateTime, v))
|
||||
}
|
||||
|
||||
// UpdateTimeContains applies the Contains predicate on the "update_time" field.
|
||||
func UpdateTimeContains(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
vc := v.String()
|
||||
return predicate.CourseEntity(sql.FieldContains(FieldUpdateTime, vc))
|
||||
}
|
||||
|
||||
// UpdateTimeHasPrefix applies the HasPrefix predicate on the "update_time" field.
|
||||
func UpdateTimeHasPrefix(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
vc := v.String()
|
||||
return predicate.CourseEntity(sql.FieldHasPrefix(FieldUpdateTime, vc))
|
||||
}
|
||||
|
||||
// UpdateTimeHasSuffix applies the HasSuffix predicate on the "update_time" field.
|
||||
func UpdateTimeHasSuffix(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
vc := v.String()
|
||||
return predicate.CourseEntity(sql.FieldHasSuffix(FieldUpdateTime, vc))
|
||||
}
|
||||
|
||||
// UpdateTimeEqualFold applies the EqualFold predicate on the "update_time" field.
|
||||
func UpdateTimeEqualFold(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
vc := v.String()
|
||||
return predicate.CourseEntity(sql.FieldEqualFold(FieldUpdateTime, vc))
|
||||
}
|
||||
|
||||
// UpdateTimeContainsFold applies the ContainsFold predicate on the "update_time" field.
|
||||
func UpdateTimeContainsFold(v *ent2.CustomTime) predicate.CourseEntity {
|
||||
vc := v.String()
|
||||
return predicate.CourseEntity(sql.FieldContainsFold(FieldUpdateTime, vc))
|
||||
}
|
||||
|
||||
// UserIDEQ applies the EQ predicate on the "user_id" field.
|
||||
func UserIDEQ(v int64) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldUserID, v))
|
||||
}
|
||||
|
||||
// UserIDNEQ applies the NEQ predicate on the "user_id" field.
|
||||
func UserIDNEQ(v int64) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNEQ(FieldUserID, v))
|
||||
}
|
||||
|
||||
// UserIDIn applies the In predicate on the "user_id" field.
|
||||
func UserIDIn(vs ...int64) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIn(FieldUserID, vs...))
|
||||
}
|
||||
|
||||
// UserIDNotIn applies the NotIn predicate on the "user_id" field.
|
||||
func UserIDNotIn(vs ...int64) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotIn(FieldUserID, vs...))
|
||||
}
|
||||
|
||||
// UsernameEQ applies the EQ predicate on the "username" field.
|
||||
func UsernameEQ(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldUsername, v))
|
||||
}
|
||||
|
||||
// UsernameNEQ applies the NEQ predicate on the "username" field.
|
||||
func UsernameNEQ(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNEQ(FieldUsername, v))
|
||||
}
|
||||
|
||||
// UsernameIn applies the In predicate on the "username" field.
|
||||
func UsernameIn(vs ...string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIn(FieldUsername, vs...))
|
||||
}
|
||||
|
||||
// UsernameNotIn applies the NotIn predicate on the "username" field.
|
||||
func UsernameNotIn(vs ...string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotIn(FieldUsername, vs...))
|
||||
}
|
||||
|
||||
// UsernameGT applies the GT predicate on the "username" field.
|
||||
func UsernameGT(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGT(FieldUsername, v))
|
||||
}
|
||||
|
||||
// UsernameGTE applies the GTE predicate on the "username" field.
|
||||
func UsernameGTE(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGTE(FieldUsername, v))
|
||||
}
|
||||
|
||||
// UsernameLT applies the LT predicate on the "username" field.
|
||||
func UsernameLT(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLT(FieldUsername, v))
|
||||
}
|
||||
|
||||
// UsernameLTE applies the LTE predicate on the "username" field.
|
||||
func UsernameLTE(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLTE(FieldUsername, v))
|
||||
}
|
||||
|
||||
// UsernameContains applies the Contains predicate on the "username" field.
|
||||
func UsernameContains(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldContains(FieldUsername, v))
|
||||
}
|
||||
|
||||
// UsernameHasPrefix applies the HasPrefix predicate on the "username" field.
|
||||
func UsernameHasPrefix(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldHasPrefix(FieldUsername, v))
|
||||
}
|
||||
|
||||
// UsernameHasSuffix applies the HasSuffix predicate on the "username" field.
|
||||
func UsernameHasSuffix(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldHasSuffix(FieldUsername, v))
|
||||
}
|
||||
|
||||
// UsernameEqualFold applies the EqualFold predicate on the "username" field.
|
||||
func UsernameEqualFold(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEqualFold(FieldUsername, v))
|
||||
}
|
||||
|
||||
// UsernameContainsFold applies the ContainsFold predicate on the "username" field.
|
||||
func UsernameContainsFold(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldContainsFold(FieldUsername, v))
|
||||
}
|
||||
|
||||
// PasswordEQ applies the EQ predicate on the "password" field.
|
||||
func PasswordEQ(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldPassword, v))
|
||||
}
|
||||
|
||||
// PasswordNEQ applies the NEQ predicate on the "password" field.
|
||||
func PasswordNEQ(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNEQ(FieldPassword, v))
|
||||
}
|
||||
|
||||
// PasswordIn applies the In predicate on the "password" field.
|
||||
func PasswordIn(vs ...string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIn(FieldPassword, vs...))
|
||||
}
|
||||
|
||||
// PasswordNotIn applies the NotIn predicate on the "password" field.
|
||||
func PasswordNotIn(vs ...string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotIn(FieldPassword, vs...))
|
||||
}
|
||||
|
||||
// PasswordGT applies the GT predicate on the "password" field.
|
||||
func PasswordGT(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGT(FieldPassword, v))
|
||||
}
|
||||
|
||||
// PasswordGTE applies the GTE predicate on the "password" field.
|
||||
func PasswordGTE(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGTE(FieldPassword, v))
|
||||
}
|
||||
|
||||
// PasswordLT applies the LT predicate on the "password" field.
|
||||
func PasswordLT(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLT(FieldPassword, v))
|
||||
}
|
||||
|
||||
// PasswordLTE applies the LTE predicate on the "password" field.
|
||||
func PasswordLTE(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLTE(FieldPassword, v))
|
||||
}
|
||||
|
||||
// PasswordContains applies the Contains predicate on the "password" field.
|
||||
func PasswordContains(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldContains(FieldPassword, v))
|
||||
}
|
||||
|
||||
// PasswordHasPrefix applies the HasPrefix predicate on the "password" field.
|
||||
func PasswordHasPrefix(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldHasPrefix(FieldPassword, v))
|
||||
}
|
||||
|
||||
// PasswordHasSuffix applies the HasSuffix predicate on the "password" field.
|
||||
func PasswordHasSuffix(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldHasSuffix(FieldPassword, v))
|
||||
}
|
||||
|
||||
// PasswordEqualFold applies the EqualFold predicate on the "password" field.
|
||||
func PasswordEqualFold(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEqualFold(FieldPassword, v))
|
||||
}
|
||||
|
||||
// PasswordContainsFold applies the ContainsFold predicate on the "password" field.
|
||||
func PasswordContainsFold(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldContainsFold(FieldPassword, v))
|
||||
}
|
||||
|
||||
// TypeEQ applies the EQ predicate on the "type" field.
|
||||
func TypeEQ(v Type) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldType, v))
|
||||
}
|
||||
|
||||
// TypeNEQ applies the NEQ predicate on the "type" field.
|
||||
func TypeNEQ(v Type) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNEQ(FieldType, v))
|
||||
}
|
||||
|
||||
// TypeIn applies the In predicate on the "type" field.
|
||||
func TypeIn(vs ...Type) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIn(FieldType, vs...))
|
||||
}
|
||||
|
||||
// TypeNotIn applies the NotIn predicate on the "type" field.
|
||||
func TypeNotIn(vs ...Type) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotIn(FieldType, vs...))
|
||||
}
|
||||
|
||||
// CourseNameEQ applies the EQ predicate on the "course_name" field.
|
||||
func CourseNameEQ(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldCourseName, v))
|
||||
}
|
||||
|
||||
// CourseNameNEQ applies the NEQ predicate on the "course_name" field.
|
||||
func CourseNameNEQ(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNEQ(FieldCourseName, v))
|
||||
}
|
||||
|
||||
// CourseNameIn applies the In predicate on the "course_name" field.
|
||||
func CourseNameIn(vs ...string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIn(FieldCourseName, vs...))
|
||||
}
|
||||
|
||||
// CourseNameNotIn applies the NotIn predicate on the "course_name" field.
|
||||
func CourseNameNotIn(vs ...string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotIn(FieldCourseName, vs...))
|
||||
}
|
||||
|
||||
// CourseNameGT applies the GT predicate on the "course_name" field.
|
||||
func CourseNameGT(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGT(FieldCourseName, v))
|
||||
}
|
||||
|
||||
// CourseNameGTE applies the GTE predicate on the "course_name" field.
|
||||
func CourseNameGTE(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGTE(FieldCourseName, v))
|
||||
}
|
||||
|
||||
// CourseNameLT applies the LT predicate on the "course_name" field.
|
||||
func CourseNameLT(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLT(FieldCourseName, v))
|
||||
}
|
||||
|
||||
// CourseNameLTE applies the LTE predicate on the "course_name" field.
|
||||
func CourseNameLTE(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLTE(FieldCourseName, v))
|
||||
}
|
||||
|
||||
// CourseNameContains applies the Contains predicate on the "course_name" field.
|
||||
func CourseNameContains(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldContains(FieldCourseName, v))
|
||||
}
|
||||
|
||||
// CourseNameHasPrefix applies the HasPrefix predicate on the "course_name" field.
|
||||
func CourseNameHasPrefix(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldHasPrefix(FieldCourseName, v))
|
||||
}
|
||||
|
||||
// CourseNameHasSuffix applies the HasSuffix predicate on the "course_name" field.
|
||||
func CourseNameHasSuffix(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldHasSuffix(FieldCourseName, v))
|
||||
}
|
||||
|
||||
// CourseNameEqualFold applies the EqualFold predicate on the "course_name" field.
|
||||
func CourseNameEqualFold(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEqualFold(FieldCourseName, v))
|
||||
}
|
||||
|
||||
// CourseNameContainsFold applies the ContainsFold predicate on the "course_name" field.
|
||||
func CourseNameContainsFold(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldContainsFold(FieldCourseName, v))
|
||||
}
|
||||
|
||||
// CourseIDEQ applies the EQ predicate on the "course_id" field.
|
||||
func CourseIDEQ(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldCourseID, v))
|
||||
}
|
||||
|
||||
// CourseIDNEQ applies the NEQ predicate on the "course_id" field.
|
||||
func CourseIDNEQ(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNEQ(FieldCourseID, v))
|
||||
}
|
||||
|
||||
// CourseIDIn applies the In predicate on the "course_id" field.
|
||||
func CourseIDIn(vs ...string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIn(FieldCourseID, vs...))
|
||||
}
|
||||
|
||||
// CourseIDNotIn applies the NotIn predicate on the "course_id" field.
|
||||
func CourseIDNotIn(vs ...string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotIn(FieldCourseID, vs...))
|
||||
}
|
||||
|
||||
// CourseIDGT applies the GT predicate on the "course_id" field.
|
||||
func CourseIDGT(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGT(FieldCourseID, v))
|
||||
}
|
||||
|
||||
// CourseIDGTE applies the GTE predicate on the "course_id" field.
|
||||
func CourseIDGTE(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGTE(FieldCourseID, v))
|
||||
}
|
||||
|
||||
// CourseIDLT applies the LT predicate on the "course_id" field.
|
||||
func CourseIDLT(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLT(FieldCourseID, v))
|
||||
}
|
||||
|
||||
// CourseIDLTE applies the LTE predicate on the "course_id" field.
|
||||
func CourseIDLTE(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLTE(FieldCourseID, v))
|
||||
}
|
||||
|
||||
// CourseIDContains applies the Contains predicate on the "course_id" field.
|
||||
func CourseIDContains(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldContains(FieldCourseID, v))
|
||||
}
|
||||
|
||||
// CourseIDHasPrefix applies the HasPrefix predicate on the "course_id" field.
|
||||
func CourseIDHasPrefix(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldHasPrefix(FieldCourseID, v))
|
||||
}
|
||||
|
||||
// CourseIDHasSuffix applies the HasSuffix predicate on the "course_id" field.
|
||||
func CourseIDHasSuffix(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldHasSuffix(FieldCourseID, v))
|
||||
}
|
||||
|
||||
// CourseIDEqualFold applies the EqualFold predicate on the "course_id" field.
|
||||
func CourseIDEqualFold(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEqualFold(FieldCourseID, v))
|
||||
}
|
||||
|
||||
// CourseIDContainsFold applies the ContainsFold predicate on the "course_id" field.
|
||||
func CourseIDContainsFold(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldContainsFold(FieldCourseID, v))
|
||||
}
|
||||
|
||||
// NeedEQ applies the EQ predicate on the "need" field.
|
||||
func NeedEQ(v Need) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldNeed, v))
|
||||
}
|
||||
|
||||
// NeedNEQ applies the NEQ predicate on the "need" field.
|
||||
func NeedNEQ(v Need) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNEQ(FieldNeed, v))
|
||||
}
|
||||
|
||||
// NeedIn applies the In predicate on the "need" field.
|
||||
func NeedIn(vs ...Need) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIn(FieldNeed, vs...))
|
||||
}
|
||||
|
||||
// NeedNotIn applies the NotIn predicate on the "need" field.
|
||||
func NeedNotIn(vs ...Need) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotIn(FieldNeed, vs...))
|
||||
}
|
||||
|
||||
// StatusEQ applies the EQ predicate on the "status" field.
|
||||
func StatusEQ(v Status) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldStatus, v))
|
||||
}
|
||||
|
||||
// StatusNEQ applies the NEQ predicate on the "status" field.
|
||||
func StatusNEQ(v Status) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNEQ(FieldStatus, v))
|
||||
}
|
||||
|
||||
// StatusIn applies the In predicate on the "status" field.
|
||||
func StatusIn(vs ...Status) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIn(FieldStatus, vs...))
|
||||
}
|
||||
|
||||
// StatusNotIn applies the NotIn predicate on the "status" field.
|
||||
func StatusNotIn(vs ...Status) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotIn(FieldStatus, vs...))
|
||||
}
|
||||
|
||||
// TaskCountEQ applies the EQ predicate on the "task_count" field.
|
||||
func TaskCountEQ(v int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldTaskCount, v))
|
||||
}
|
||||
|
||||
// TaskCountNEQ applies the NEQ predicate on the "task_count" field.
|
||||
func TaskCountNEQ(v int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNEQ(FieldTaskCount, v))
|
||||
}
|
||||
|
||||
// TaskCountIn applies the In predicate on the "task_count" field.
|
||||
func TaskCountIn(vs ...int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIn(FieldTaskCount, vs...))
|
||||
}
|
||||
|
||||
// TaskCountNotIn applies the NotIn predicate on the "task_count" field.
|
||||
func TaskCountNotIn(vs ...int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotIn(FieldTaskCount, vs...))
|
||||
}
|
||||
|
||||
// TaskCountGT applies the GT predicate on the "task_count" field.
|
||||
func TaskCountGT(v int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGT(FieldTaskCount, v))
|
||||
}
|
||||
|
||||
// TaskCountGTE applies the GTE predicate on the "task_count" field.
|
||||
func TaskCountGTE(v int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGTE(FieldTaskCount, v))
|
||||
}
|
||||
|
||||
// TaskCountLT applies the LT predicate on the "task_count" field.
|
||||
func TaskCountLT(v int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLT(FieldTaskCount, v))
|
||||
}
|
||||
|
||||
// TaskCountLTE applies the LTE predicate on the "task_count" field.
|
||||
func TaskCountLTE(v int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLTE(FieldTaskCount, v))
|
||||
}
|
||||
|
||||
// SuccessCountEQ applies the EQ predicate on the "success_count" field.
|
||||
func SuccessCountEQ(v int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldSuccessCount, v))
|
||||
}
|
||||
|
||||
// SuccessCountNEQ applies the NEQ predicate on the "success_count" field.
|
||||
func SuccessCountNEQ(v int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNEQ(FieldSuccessCount, v))
|
||||
}
|
||||
|
||||
// SuccessCountIn applies the In predicate on the "success_count" field.
|
||||
func SuccessCountIn(vs ...int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIn(FieldSuccessCount, vs...))
|
||||
}
|
||||
|
||||
// SuccessCountNotIn applies the NotIn predicate on the "success_count" field.
|
||||
func SuccessCountNotIn(vs ...int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotIn(FieldSuccessCount, vs...))
|
||||
}
|
||||
|
||||
// SuccessCountGT applies the GT predicate on the "success_count" field.
|
||||
func SuccessCountGT(v int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGT(FieldSuccessCount, v))
|
||||
}
|
||||
|
||||
// SuccessCountGTE applies the GTE predicate on the "success_count" field.
|
||||
func SuccessCountGTE(v int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGTE(FieldSuccessCount, v))
|
||||
}
|
||||
|
||||
// SuccessCountLT applies the LT predicate on the "success_count" field.
|
||||
func SuccessCountLT(v int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLT(FieldSuccessCount, v))
|
||||
}
|
||||
|
||||
// SuccessCountLTE applies the LTE predicate on the "success_count" field.
|
||||
func SuccessCountLTE(v int) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLTE(FieldSuccessCount, v))
|
||||
}
|
||||
|
||||
// ExamStateEQ applies the EQ predicate on the "exam_state" field.
|
||||
func ExamStateEQ(v ExamState) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldExamState, v))
|
||||
}
|
||||
|
||||
// ExamStateNEQ applies the NEQ predicate on the "exam_state" field.
|
||||
func ExamStateNEQ(v ExamState) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNEQ(FieldExamState, v))
|
||||
}
|
||||
|
||||
// ExamStateIn applies the In predicate on the "exam_state" field.
|
||||
func ExamStateIn(vs ...ExamState) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIn(FieldExamState, vs...))
|
||||
}
|
||||
|
||||
// ExamStateNotIn applies the NotIn predicate on the "exam_state" field.
|
||||
func ExamStateNotIn(vs ...ExamState) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotIn(FieldExamState, vs...))
|
||||
}
|
||||
|
||||
// ViolenceEQ applies the EQ predicate on the "violence" field.
|
||||
func ViolenceEQ(v Violence) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldViolence, v))
|
||||
}
|
||||
|
||||
// ViolenceNEQ applies the NEQ predicate on the "violence" field.
|
||||
func ViolenceNEQ(v Violence) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNEQ(FieldViolence, v))
|
||||
}
|
||||
|
||||
// ViolenceIn applies the In predicate on the "violence" field.
|
||||
func ViolenceIn(vs ...Violence) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIn(FieldViolence, vs...))
|
||||
}
|
||||
|
||||
// ViolenceNotIn applies the NotIn predicate on the "violence" field.
|
||||
func ViolenceNotIn(vs ...Violence) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotIn(FieldViolence, vs...))
|
||||
}
|
||||
|
||||
// DescEQ applies the EQ predicate on the "desc" field.
|
||||
func DescEQ(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEQ(FieldDesc, v))
|
||||
}
|
||||
|
||||
// DescNEQ applies the NEQ predicate on the "desc" field.
|
||||
func DescNEQ(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNEQ(FieldDesc, v))
|
||||
}
|
||||
|
||||
// DescIn applies the In predicate on the "desc" field.
|
||||
func DescIn(vs ...string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIn(FieldDesc, vs...))
|
||||
}
|
||||
|
||||
// DescNotIn applies the NotIn predicate on the "desc" field.
|
||||
func DescNotIn(vs ...string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotIn(FieldDesc, vs...))
|
||||
}
|
||||
|
||||
// DescGT applies the GT predicate on the "desc" field.
|
||||
func DescGT(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGT(FieldDesc, v))
|
||||
}
|
||||
|
||||
// DescGTE applies the GTE predicate on the "desc" field.
|
||||
func DescGTE(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldGTE(FieldDesc, v))
|
||||
}
|
||||
|
||||
// DescLT applies the LT predicate on the "desc" field.
|
||||
func DescLT(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLT(FieldDesc, v))
|
||||
}
|
||||
|
||||
// DescLTE applies the LTE predicate on the "desc" field.
|
||||
func DescLTE(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldLTE(FieldDesc, v))
|
||||
}
|
||||
|
||||
// DescContains applies the Contains predicate on the "desc" field.
|
||||
func DescContains(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldContains(FieldDesc, v))
|
||||
}
|
||||
|
||||
// DescHasPrefix applies the HasPrefix predicate on the "desc" field.
|
||||
func DescHasPrefix(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldHasPrefix(FieldDesc, v))
|
||||
}
|
||||
|
||||
// DescHasSuffix applies the HasSuffix predicate on the "desc" field.
|
||||
func DescHasSuffix(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldHasSuffix(FieldDesc, v))
|
||||
}
|
||||
|
||||
// DescIsNil applies the IsNil predicate on the "desc" field.
|
||||
func DescIsNil() predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldIsNull(FieldDesc))
|
||||
}
|
||||
|
||||
// DescNotNil applies the NotNil predicate on the "desc" field.
|
||||
func DescNotNil() predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldNotNull(FieldDesc))
|
||||
}
|
||||
|
||||
// DescEqualFold applies the EqualFold predicate on the "desc" field.
|
||||
func DescEqualFold(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldEqualFold(FieldDesc, v))
|
||||
}
|
||||
|
||||
// DescContainsFold applies the ContainsFold predicate on the "desc" field.
|
||||
func DescContainsFold(v string) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.FieldContainsFold(FieldDesc, v))
|
||||
}
|
||||
|
||||
// HasSysUser applies the HasEdge predicate on the "sys_user" edge.
|
||||
func HasSysUser() predicate.CourseEntity {
|
||||
return predicate.CourseEntity(func(s *sql.Selector) {
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, SysUserTable, SysUserColumn),
|
||||
)
|
||||
schemaConfig := internal.SchemaConfigFromContext(s.Context())
|
||||
step.To.Schema = schemaConfig.SysUser
|
||||
step.Edge.Schema = schemaConfig.CourseEntity
|
||||
sqlgraph.HasNeighbors(s, step)
|
||||
})
|
||||
}
|
||||
|
||||
// HasSysUserWith applies the HasEdge predicate on the "sys_user" edge with a given conditions (other predicates).
|
||||
func HasSysUserWith(preds ...predicate.SysUser) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(func(s *sql.Selector) {
|
||||
step := newSysUserStep()
|
||||
schemaConfig := internal.SchemaConfigFromContext(s.Context())
|
||||
step.To.Schema = schemaConfig.SysUser
|
||||
step.Edge.Schema = schemaConfig.CourseEntity
|
||||
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
|
||||
for _, p := range preds {
|
||||
p(s)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// And groups predicates with the AND operator between them.
|
||||
func And(predicates ...predicate.CourseEntity) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.AndPredicates(predicates...))
|
||||
}
|
||||
|
||||
// Or groups predicates with the OR operator between them.
|
||||
func Or(predicates ...predicate.CourseEntity) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.OrPredicates(predicates...))
|
||||
}
|
||||
|
||||
// Not applies the not operator on the given predicate.
|
||||
func Not(p predicate.CourseEntity) predicate.CourseEntity {
|
||||
return predicate.CourseEntity(sql.NotPredicates(p))
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user