diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..1f020b7
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/online_code.iml b/.idea/online_code.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/.idea/online_code.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/constant/rds_key.go b/common/constant/rds_key.go
new file mode 100644
index 0000000..6d1864d
--- /dev/null
+++ b/common/constant/rds_key.go
@@ -0,0 +1,6 @@
+package constant
+
+const (
+ OAuth2RedisKey = "oauth:token:" // Token缓存前缀
+ OAuth2UserCacheKey = "oauth:user:" // 用户缓存前缀
+)
diff --git a/common/constant/user.go b/common/constant/user.go
new file mode 100644
index 0000000..47b22e1
--- /dev/null
+++ b/common/constant/user.go
@@ -0,0 +1,38 @@
+package constant
+
+// UserStatus 用户状态
+type UserStatus string
+
+const (
+ UserStatusActive UserStatus = "NORMAL" // 用户状态正常
+ UserStatusDisabled UserStatus = "DISABLE" // 已禁用用户
+)
+
+// 状态对应的描述
+var userStatusMap = map[UserStatus]string{
+ UserStatusActive: "正常",
+ UserStatusDisabled: "已禁用",
+}
+
+// 处理为看得懂的状态
+func (s UserStatus) String() string {
+ if str, ok := userStatusMap[s]; ok {
+ return str
+ }
+ return string(s)
+}
+
+// =====================================================================================================================
+
+// UserIdentity 用户身份
+type UserIdentity string
+
+const (
+ UserIdentityAdmin UserIdentity = "admin" // 管理员
+ UserIdentityUser UserIdentity = "user" // 普通用户
+)
+
+// String implements the Stringer interface.
+func (t UserIdentity) String() string {
+ return string(t)
+}
diff --git a/common/default_keys.go b/common/default_keys.go
new file mode 100644
index 0000000..31f9cfe
--- /dev/null
+++ b/common/default_keys.go
@@ -0,0 +1,6 @@
+package common
+
+const (
+ SmsSendKey = "sms:send:" // 短信发送缓存前缀
+ Oauth2RedisKey = "oauth:token:" // Token缓存前缀
+)
diff --git a/common/types/date.go b/common/types/date.go
new file mode 100644
index 0000000..bb842f0
--- /dev/null
+++ b/common/types/date.go
@@ -0,0 +1,105 @@
+package types
+
+import (
+ "database/sql/driver"
+ "fmt"
+ "time"
+)
+
+// 默认时间格式
+const dateFormat = "2006-01-02 15:04:05.000"
+
+// DateTime 自定义时间类型
+type DateTime time.Time
+
+// Scan implements the Scanner interface.
+func (dt *DateTime) Scan(value interface{}) error {
+ // mysql 内部日期的格式可能是 2006-01-02 15:04:05 +0800 CST 格式,所以检出的时候还需要进行一次格式化
+ tTime, _ := time.Parse("2006-01-02 15:04:05 +0800 CST", value.(time.Time).String())
+ *dt = DateTime(tTime)
+ return nil
+}
+
+// Value implements the driver Valuer interface.
+func (dt DateTime) Value() (driver.Value, error) {
+ // 0001-01-01 00:00:00 属于空值,遇到空值解析成 null 即可
+ if dt.String() == "0001-01-01 00:00:00.000" {
+ return nil, nil
+ }
+ return []byte(dt.Format(dateFormat)), nil
+}
+
+// 用于 fmt.Println 和后续验证场景
+func (dt DateTime) String() string {
+ return dt.Format(dateFormat)
+}
+
+// Format 格式化
+func (dt DateTime) Format(fm string) string {
+ return time.Time(dt).Format(fm)
+}
+
+// After 时间比较
+func (dt *DateTime) After(now time.Time) bool {
+ return time.Time(*dt).After(now)
+}
+
+// Before 时间比较
+func (dt *DateTime) Before(now time.Time) bool {
+ return time.Time(*dt).Before(now)
+}
+
+// IBefore 时间比较
+func (dt *DateTime) IBefore(now DateTime) bool {
+ return dt.Before(time.Time(now))
+}
+
+// SubTime 对比
+func (dt DateTime) SubTime(t time.Time) time.Duration {
+ return dt.ToTime().Sub(t)
+}
+
+// Sub 对比
+func (dt DateTime) Sub(t DateTime) time.Duration {
+ return dt.ToTime().Sub(t.ToTime())
+}
+
+// ToTime 转换为golang的时间类型
+func (dt DateTime) ToTime() time.Time {
+ return time.Time(dt)
+}
+
+// IsNil 是否为空值
+func (dt DateTime) IsNil() bool {
+ return dt.Format(dateFormat) == "0001-01-01 00:00:00.000"
+}
+
+// Unix 实现Unix函数
+func (dt DateTime) Unix() int64 {
+ return dt.ToTime().Unix()
+}
+
+// ======== 序列化 ========
+
+// MarshalJSON 时间到字符串
+func (dt DateTime) MarshalJSON() ([]byte, error) {
+ // 过滤掉空数据
+ if dt.IsNil() {
+ return []byte("\"\""), nil
+ }
+ output := fmt.Sprintf(`"%s"`, dt.Format("2006-01-02 15:04:05"))
+ return []byte(output), nil
+}
+
+// UnmarshalJSON 字符串到时间
+func (dt *DateTime) UnmarshalJSON(b []byte) error {
+ if len(b) == 2 {
+ *dt = DateTime{}
+ return nil
+ }
+ // 解析指定的格式
+ //now, err := time.ParseInLocation(`"`+dateFormat+`"`, string(b), time.Local)
+ now, err := time.ParseInLocation(dateFormat, string(b), time.Local)
+ *dt = DateTime(now)
+ return err
+}
diff --git a/common/types/model.go b/common/types/model.go
new file mode 100644
index 0000000..8982a85
--- /dev/null
+++ b/common/types/model.go
@@ -0,0 +1,20 @@
+package types
+
+import (
+ "github.com/google/uuid"
+ "gorm.io/gorm"
+)
+
+// BaseDbModel 数据库通用字段
+type BaseDbModel struct {
+ Id string `json:"id" gorm:"type:varchar(50);primarykey"`
+ CreatedAt DateTime `json:"createdAt"`
+ UpdatedAt DateTime `json:"updatedAt"`
+ DeletedAt gorm.DeletedAt `json:"-" gorm:"index:deleted"`
+}
+
+// BeforeCreate 创建数据库对象之前生成UUID
+func (m *BaseDbModel) BeforeCreate(*gorm.DB) (err error) {
+ m.Id = uuid.New().String()
+ return
+}
diff --git a/core/error_handle.go b/core/error_handle.go
new file mode 100644
index 0000000..470224e
--- /dev/null
+++ b/core/error_handle.go
@@ -0,0 +1,52 @@
+package core
+
+import (
+ "fmt"
+ "git.echol.cn/loser/logger/log"
+ "github.com/gin-gonic/gin"
+ "net"
+ "net/http"
+ "os"
+ "strings"
+)
+
+// NoMethodHandler 请求方式不对
+func NoMethodHandler() gin.HandlerFunc {
+ return func(ctx *gin.Context) {
+ R(ctx).FailWithMessageAndCode(fmt.Sprintf("不支持%v请求", ctx.Request.Method), http.StatusMethodNotAllowed)
+ ctx.Abort()
+ }
+}
+
+// NoRouteHandler 404异常处理
+func NoRouteHandler() gin.HandlerFunc {
+ return func(ctx *gin.Context) {
+ R(ctx).FailWithMessageAndCode("请求接口不存在", http.StatusNotFound)
+ ctx.Abort()
+ }
+}
+
+// Recovery Panic捕获
+func Recovery() gin.HandlerFunc {
+ return func(ctx *gin.Context) {
+ defer func() {
+ if err := recover(); err != nil {
+ log.Errorf("系统错误: %v", err)
+ var brokenPipe bool
+ if ne, ok := err.(*net.OpError); ok {
+ if se, ok := ne.Err.(*os.SyscallError); ok {
+ if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
+ brokenPipe = true
+ }
+ }
+ }
+ if brokenPipe {
+ log.Errorf("%s", err)
+ }
+ R(ctx).FailWithMessage("服务器异常,请联系管理员")
+ ctx.Abort()
+ }
+ }()
+ ctx.Next()
+ }
+}
diff --git a/core/response.go b/core/response.go
new file mode 100644
index 0000000..086016e
--- /dev/null
+++ b/core/response.go
@@ -0,0 +1,75 @@
+package core
+
+import (
+ "github.com/gin-gonic/gin"
+ "net/http"
+)
+
+// 返回数据包装
+type response struct {
+ Code int `json:"code"`
+ Data any `json:"data"`
+ Msg string `json:"message"`
+}
+
+type rs struct {
+ ctx *gin.Context
+}
+
+// 定义状态码
+const (
+ ERROR = http.StatusInternalServerError
+ SUCCESS = http.StatusOK
+)
+
+func R(ctx *gin.Context) *rs {
+ return &rs{ctx}
+}
+
+// Result 手动组装返回结果
+func (r rs) Result(code int, data interface{}, msg string) {
+ //if data == nil {
+ // data = map[string]interface{}{}
+ //}
+
+ r.ctx.JSON(code, response{
+ code,
+ data,
+ msg,
+ })
+}
+
+// Ok 返回无数据的成功
+func (r rs) Ok() {
+ r.Result(SUCCESS, nil, "操作成功")
+}
+
+// OkWithMessage 返回自定义成功的消息
+func (r rs) OkWithMessage(message string) {
+ r.Result(SUCCESS, nil, message)
+}
+
+// OkWithData 自定义内容的成功返回
+func (r rs) OkWithData(data interface{}) {
+ r.Result(SUCCESS, data, "操作成功")
+}
+
+// OkDetailed 自定义消息和内容的成功返回
+func (r rs) OkDetailed(data interface{}, message string) {
+ r.Result(SUCCESS, data, message)
+}
+
+// Fail 返回默认失败
+func (r rs) Fail() {
+ r.Result(ERROR, nil, "操作失败")
+}
+
+// FailWithMessage 返回默认状态码自定义消息的失败
+func (r rs) FailWithMessage(message string) {
+ r.Result(ERROR, nil, message)
+}
+
+// FailWithMessageAndCode 返回自定义消息和状态码的失败
+func (r rs) FailWithMessageAndCode(message string, code int) {
+ r.Result(code, nil, message)
+}
diff --git a/core/response_page.go b/core/response_page.go
new file mode 100644
index 0000000..eb588f2
--- /dev/null
+++ b/core/response_page.go
@@ -0,0 +1,10 @@
+package core
+
+// PageData 分页数据通用结构体
+type PageData struct {
+ Current int `json:"current"` // 当前页码
+ Size int `json:"size"` // 每页数量
+ Total int64 `json:"total"` // 总数
+ TotalPage int `json:"total_page"` // 总页数
+ Records interface{} `json:"records"` // 返回数据
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..029e83e
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,59 @@
+module online_code
+
+go 1.18
+
+require (
+ git.echol.cn/loser/logger v1.0.14
+ github.com/gin-gonic/gin v1.7.7
+ github.com/google/uuid v1.1.2
+ gorm.io/gorm v1.23.5
+)
+
+require (
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/caarlos0/env/v6 v6.9.2 // indirect
+ github.com/cespare/xxhash/v2 v2.1.2 // indirect
+ github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/go-kit/kit v0.12.0 // indirect
+ github.com/go-kit/log v0.2.1 // indirect
+ github.com/go-logfmt/logfmt v0.5.1 // indirect
+ github.com/go-playground/locales v0.13.0 // indirect
+ github.com/go-playground/universal-translator v0.17.0 // indirect
+ github.com/go-playground/validator/v10 v10.4.1 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.2 // indirect
+ github.com/golang/snappy v0.0.4 // indirect
+ github.com/jinzhu/inflection v1.0.0 // indirect
+ github.com/jinzhu/now v1.1.4 // indirect
+ github.com/jpillora/backoff v1.0.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/leodido/go-urn v1.2.0 // indirect
+ github.com/lixh00/loki-client-go v1.0.1 // indirect
+ github.com/mattn/go-isatty v0.0.14 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
+ github.com/natefinch/lumberjack v2.0.0+incompatible // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/prometheus/client_golang v1.12.2 // indirect
+ github.com/prometheus/client_model v0.2.0 // indirect
+ github.com/prometheus/common v0.34.0 // indirect
+ github.com/prometheus/procfs v0.7.3 // indirect
+ github.com/prometheus/prometheus v1.8.2-0.20201028100903-3245b3267b24 // indirect
+ github.com/ugorji/go/codec v1.1.7 // indirect
+ go.uber.org/atomic v1.9.0 // indirect
+ go.uber.org/multierr v1.8.0 // indirect
+ go.uber.org/zap v1.21.0 // indirect
+ golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 // indirect
+ golang.org/x/net v0.0.0-20220517181318-183a9ca12b87 // indirect
+ golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
+ golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e // indirect
+ golang.org/x/text v0.3.7 // indirect
+ google.golang.org/appengine v1.6.7 // indirect
+ google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 // indirect
+ google.golang.org/grpc v1.46.2 // indirect
+ google.golang.org/protobuf v1.28.0 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+)
diff --git a/models/entity/category.go b/models/entity/category.go
new file mode 100644
index 0000000..d86a17d
--- /dev/null
+++ b/models/entity/category.go
@@ -0,0 +1,15 @@
+package entity
+
+import "online_code/common/types"
+
+// Category 分类表
+type Category struct {
+ types.BaseDbModel
+ Identity string `json:"identity" gorm:"column:identity;type:varchar(36);comment:'分类的唯一标识'"`
+ Name string `json:"name" gorm:"column:name;type:varchar(100);comment:'分类名称'" `
+ ParentId int `json:"parent_id" gorm:"column:parent_id;type:int(11);comment:'父级ID'" `
+}
+
+func (table *Category) TableName() string {
+ return "category"
+}
diff --git a/models/entity/problem.go b/models/entity/problem.go
new file mode 100644
index 0000000..40183e0
--- /dev/null
+++ b/models/entity/problem.go
@@ -0,0 +1,21 @@
+package entity
+
+import "online_code/common/types"
+
+// Problem 问题表
+type Problem struct {
+ types.BaseDbModel
+ Identity string `json:"identity" gorm:"column:identity;type:varchar(36);comment:'问题表的唯一标识'" ` // 问题表的唯一标识
+ ProblemCategories []*ProblemCategory `json:"problem_categories" gorm:"foreignKey:problem_id;references:id;comment:'关联问题分类表'" ` // 关联问题分类表
+ Title string `json:"title" gorm:"column:title;type:varchar(255);comment:'文章标题'" ` // 文章标题
+ Content string `json:"content" gorm:"column:content;type:text;comment:'文章正文'" ` // 文章正文
+ MaxRuntime int `json:"max_runtime" gorm:"column:max_runtime;type:int(11);comment:'最大运行时长'" ` // 最大运行时长
+ MaxMem int `json:"max_mem" gorm:"column:max_mem;type:int(11);comment:'最大运行内存'" ` // 最大运行内存
+ TestCases []*TestCase `json:"test_cases" gorm:"foreignKey:problem_identity;references:identity;comment:'关联测试用例表'" ` // 关联测试用例表
+ PassNum int64 `json:"pass_num" gorm:"column:pass_num;type:int(11);comment:'通过次数'" ` // 通过次数
+ SubmitNum int64 `json:"submit_num" gorm:"column:submit_num;type:int(11);comment:'提交次数'" ` // 提交次数
+}
+
+func (table *Problem) TableName() string {
+ return "problem_basic"
+}
diff --git a/models/entity/problem_category.go b/models/entity/problem_category.go
new file mode 100644
index 0000000..d2a5fef
--- /dev/null
+++ b/models/entity/problem_category.go
@@ -0,0 +1,17 @@
+package entity
+
+import (
+ "online_code/common/types"
+)
+
+// ProblemCategory 问题分类表
+type ProblemCategory struct {
+ types.BaseDbModel
+ ProblemId uint `json:"problem_id" gorm:"column:problem_id;type:int(11);comment:'问题的ID'" ` // 问题的ID
+ CategoryId uint `json:"category_id" gorm:"column:category_id;type:int(11);comment:'分类的ID'" ` // 分类的ID
+ CategoryBasic *Category `json:"category_basic" gorm:"foreignKey:id;references:category_id;comment:'关联分类的基础信息表'" ` // 关联分类的基础信息表
+}
+
+func (table *ProblemCategory) TableName() string {
+ return "problem_category"
+}
diff --git a/models/entity/submit.go b/models/entity/submit.go
new file mode 100644
index 0000000..d58ea43
--- /dev/null
+++ b/models/entity/submit.go
@@ -0,0 +1,16 @@
+package entity
+
+// Submit 提交表
+type Submit struct {
+ Identity string `json:"identity" gorm:"column:identity;type:varchar(36);comment:'用户唯一标识'" ` // 唯一标识
+ ProblemIdentity string `json:"problem_identity" gorm:"column:problem_identity;type:varchar(36);comment:'问题表的唯一标识'" ` // 问题表的唯一标识
+ ProblemBasic *Problem `json:"problem_basic" gorm:"foreignKey:identity;references:problem_identity;comment:'关联问题基础表'" ` // 关联问题基础表
+ UserIdentity string `json:"user_identity" gorm:"column:user_identity;type:varchar(36);comment:'用户表的唯一标识'" ` // 用户表的唯一标识
+ UserBasic *User `json:"user_basic" gorm:"foreignKey:identity;references:user_identity;comment:'联用户基础表'" ` // 关联用户基础表
+ Path string `json:"path" gorm:"column:path;type:varchar(255);comment:'代码存放路径'" ` // 代码存放路径
+ Status int `json:"status" gorm:"column:status;type:tinyint(1);comment:'状态'" ` // 【-1-待判断,1-答案正确,2-答案错误,3-运行超时,4-运行超内存, 5-编译错误】
+}
+
+func (table *Submit) TableName() string {
+ return "submit"
+}
diff --git a/models/entity/test_case.go b/models/entity/test_case.go
new file mode 100644
index 0000000..0076d8f
--- /dev/null
+++ b/models/entity/test_case.go
@@ -0,0 +1,16 @@
+package entity
+
+import "online_code/common/types"
+
+// TestCase 测试用例
+type TestCase struct {
+ types.BaseDbModel
+ Identity string `json:"identity" gorm:"column:identity;type:varchar(255);comment:'用户唯一标识''" `
+ ProblemIdentity string `json:"problem_identity" gorm:"column:problem_identity;type:varchar(255);comment:'问题'" `
+ Input string `json:"input" gorm:"column:input;type:text;" `
+ Output string `json:"output" gorm:"column:output;type:text;" `
+}
+
+func (table *TestCase) TableName() string {
+ return "test_case"
+}
diff --git a/models/entity/user.go b/models/entity/user.go
new file mode 100644
index 0000000..516c04f
--- /dev/null
+++ b/models/entity/user.go
@@ -0,0 +1,19 @@
+package entity
+
+import "online_code/common/types"
+
+type User struct {
+ types.BaseDbModel
+ Identity string `json:"identity" gorm:"column:identity;type:varchar(36);comment:'用户唯一标识'"`
+ Name string `json:"name" gorm:"column:name;type:varchar(100);not null;comment:'用户名'"`
+ Password string `json:"password" gorm:"column:password;type:varchar(32);not null;comment:'用户密码'"`
+ Phone string `json:"phone" gorm:"column:phone;type:varchar(20);not null;comment:'用户手机号'"`
+ Mail string `json:"mail" gorm:"column:mail;type:varchar(100);not null;comment:'用户邮箱'"`
+ PassNum int64 `json:"pass_num" gorm:"column:pass_num;type:int(11);comment:'通过的次数'"`
+ SubmitNum int64 `json:"submit_num" gorm:"column:submit_num;type:int(11);comment:'提交次数'"`
+ IsAdmin int `json:"is_admin" gorm:"column:is_admin;type:tinyint(1);comment:'是否是管理员【0-否,1-是】'"`
+}
+
+func (table *User) TableName() string {
+ return "user"
+}