This commit is contained in:
孟帅
2022-02-25 17:11:17 +08:00
parent 9bd05abb2c
commit 8f3d679a57
897 changed files with 95731 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
//
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Author  Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package com
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcache"
)
type cache struct {
}
var (
Cache = new(cache)
)
func (component *cache) New() *gcache.Cache {
c := gcache.New()
//redis
adapter := gcache.NewAdapterRedis(g.Redis())
//内存
//adapter := gcache.NewAdapterMemory()
c.SetAdapter(adapter)
return c
}

View File

@@ -0,0 +1,62 @@
//
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Author  Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package com
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/text/gstr"
"github.com/mojocn/base64Captcha"
)
var Captcha = new(captcha)
type captcha struct{}
//
//  @Title  获取字母数字混合验证码
//  @Description 
//  @Author  Ms <133814250@qq.com>
//  @Param   ctx
//  @Return  idKeyC
//  @Return  base64stringC
//
func (component *captcha) GetVerifyImgString(ctx context.Context) (idKeyC string, base64stringC string) {
driver := &base64Captcha.DriverString{
Height: 80,
Width: 240,
//NoiseCount: 50,
//ShowLineOptions: 20,
Length: 4,
Source: "abcdefghjkmnpqrstuvwxyz23456789",
Fonts: []string{"chromohv.ttf"},
}
driver = driver.ConvertFonts()
store := base64Captcha.DefaultMemStore
c := base64Captcha.NewCaptcha(driver, store)
idKeyC, base64stringC, err := c.Generate()
if err != nil {
g.Log().Error(ctx,err)
}
return
}
//
//  @Title  验证输入的验证码是否正确
//  @Description 
//  @Author  Ms <133814250@qq.com>
//  @Param   id
//  @Param   answer
//  @Return  bool
//
func (component *captcha) VerifyString(id, answer string) bool {
driver := new(base64Captcha.DriverString)
store := base64Captcha.DefaultMemStore
c := base64Captcha.NewCaptcha(driver, store)
answer = gstr.ToLower(answer)
return c.Verify(id, answer, true)
}

View File

@@ -0,0 +1,107 @@
//
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Author  Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package com
import (
"context"
"github.com/bufanyun/hotgo/app/consts"
"github.com/bufanyun/hotgo/app/model"
"github.com/gogf/gf/v2/net/ghttp"
)
type comContext struct{}
var Context = new(comContext)
//
//  @Title  初始化上下文对象指针到上下文对象中,以便后续的请求流程中可以修改
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   r
//  @Param   customCtx
//
func (component *comContext) Init(r *ghttp.Request, customCtx *model.Context) {
r.SetCtxVar(consts.ContextKey, customCtx)
}
//
//  @Title  获得上下文变量如果没有设置那么返回nil
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   ctx
//  @Return  *model.Context
//
func (component *comContext) Get(ctx context.Context) *model.Context {
value := ctx.Value(consts.ContextKey)
if value == nil {
return nil
}
if localCtx, ok := value.(*model.Context); ok {
return localCtx
}
return nil
}
//
//  @Title  将上下文信息设置到上下文请求中,注意是完整覆盖
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   ctx
//  @Param   user
//
func (component *comContext) SetUser(ctx context.Context, user *model.Identity) {
component.Get(ctx).User = user
}
//
//  @Title  设置组件响应 用于全局日志使用
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   ctx
//  @Param   response
//
func (component *comContext) SetResponse(ctx context.Context, response *model.Response) {
component.Get(ctx).ComResponse = response
}
//
//  @Title  设置应用模块
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   ctx
//  @Param   module
//
func (component *comContext) SetModule(ctx context.Context, module string) {
component.Get(ctx).Module = module
}
//
//  @Title  设置请求耗时
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   ctx
//  @Param   module
//
func (component *comContext) SetTakeUpTime(ctx context.Context, takeUpTime int64) {
component.Get(ctx).TakeUpTime = takeUpTime
}
//
//  @Title  获取用户ID
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   ctx
//  @Return  int
//
func (component *comContext) GetUserId(ctx context.Context) int64 {
user := component.Get(ctx).User
if user == nil {
return 0
}
return user.Id
}

View File

@@ -0,0 +1,254 @@
//
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Author  Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package com
import (
"context"
"github.com/axgle/mahonia"
"github.com/bufanyun/hotgo/app/model/entity"
"github.com/bufanyun/hotgo/app/utils"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/kayon/iploc"
"time"
)
var Ip = new(ip)
type ip struct{}
type IpLocationData struct {
Ip string `json:"ip"`
Country string `json:"country"`
Region string `json:"region"`
Province string `json:"province"`
ProvinceCode int `json:"province_code"`
City string `json:"city"`
CityCode int `json:"city_code"`
Area string `json:"area"`
AreaCode int `json:"area_code"`
}
//
//  @Title  通过Whois接口查询IP归属地
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   ctx
//  @Param   ip
//  @Return  IpLocationData
//
func (component *ip) WhoisLocation(ctx context.Context, ip string) IpLocationData {
type whoisRegionData struct {
Ip string `json:"ip"`
Pro string `json:"pro" `
ProCode string `json:"proCode" `
City string `json:"city" `
CityCode string `json:"cityCode"`
Region string `json:"region"`
RegionCode string `json:"regionCode"`
Addr string `json:"addr"`
Err string `json:"err"`
}
if !utils.Validate.IsIp(ip) {
return IpLocationData{}
}
response, err := g.Client().Timeout(10*time.Second).Get(ctx, "http://whois.pconline.com.cn/ipJson.jsp?ip="+ip+"&json=true")
if err != nil {
err = gerror.New(err.Error())
return IpLocationData{
Ip: ip,
}
}
defer response.Close()
var enc mahonia.Decoder
enc = mahonia.NewDecoder("gbk")
data := enc.ConvertString(response.ReadAllString())
g.Log().Print(ctx, "data:", data)
whoisData := whoisRegionData{}
if err := gconv.Struct(data, &whoisData); err != nil {
err = gerror.New(err.Error())
g.Log().Print(ctx, "err:", err)
return IpLocationData{
Ip: ip,
}
}
g.Log().Print(ctx, "whoisData:", whoisData)
return IpLocationData{
Ip: whoisData.Ip,
//Country string `json:"country"`
Region: whoisData.Addr,
Province: whoisData.Pro,
ProvinceCode: gconv.Int(whoisData.ProCode),
City: whoisData.City,
CityCode: gconv.Int(whoisData.CityCode),
Area: whoisData.Region,
AreaCode: gconv.Int(whoisData.RegionCode),
}
}
//
//  @Title  通过Cz88的IP库查询IP归属地
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   ctx
//  @Param   ip
//  @Return  IpLocationData
//
func (component *ip) Cz88Find(ctx context.Context, ip string) IpLocationData {
if !utils.Validate.IsIp(ip) {
g.Log().Print(ctx, "ip格式错误:", ip)
return IpLocationData{}
}
loc, err := iploc.OpenWithoutIndexes("./storage/ip/qqwry-utf8.dat")
if err != nil {
err = gerror.New(err.Error())
return IpLocationData{
Ip: ip,
}
}
detail := loc.Find(ip)
if detail == nil {
return IpLocationData{
Ip: ip,
}
}
locationData := IpLocationData{
Ip: ip,
Country: detail.Country,
Region: detail.Region,
Province: detail.Province,
City: detail.City,
Area: detail.County,
}
if gstr.LenRune(locationData.Province) == 0 {
return locationData
}
var (
provinceModel *entity.SysProvinces
cityModel *entity.SysProvinces
areaModel *entity.SysProvinces
)
err = g.DB().Model("hg_common_provinces").
Where("level", 1).
WhereLike("title", "%"+locationData.Province+"%").
Scan(&provinceModel)
if err != nil {
err = gerror.New(err.Error())
return locationData
}
if provinceModel != nil {
locationData.ProvinceCode = provinceModel.Id
locationData.Province = provinceModel.Title
}
if gstr.LenRune(locationData.City) == 0 {
return locationData
// 是否为直辖市
} else if component.IsJurisdictionByIpTitle(locationData.City) {
locationData.CityCode = provinceModel.Id + 100
locationData.City = "直辖市"
} else {
//替换掉
locationData.City = gstr.Replace(locationData.City, "地区", "")
err = g.DB().Model("hg_common_provinces").
Where("level", 2).
Where("pid", locationData.ProvinceCode).
WhereLike("title", "%"+locationData.City+"%").
Scan(&cityModel)
if err != nil {
err = gerror.New(err.Error())
return locationData
}
if cityModel != nil {
locationData.CityCode = cityModel.Id
locationData.City = cityModel.Title
}
}
if gstr.LenRune(locationData.Area) == 0 {
return locationData
}
err = g.DB().Model("hg_common_provinces").
Where("level", 3).
Where("pid", locationData.CityCode).
WhereLike("title", "%"+locationData.Area+"%").
Scan(&areaModel)
if err != nil {
err = gerror.New(err.Error())
return locationData
}
if areaModel != nil {
locationData.AreaCode = areaModel.Id
locationData.Area = areaModel.Title
}
return locationData
}
//
//  @Title  判断地区名称是否为直辖市
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   title
//  @Return  bool
//
func (component *ip) IsJurisdictionByIpTitle(title string) bool {
lists := []string{"北京市", "天津市", "重庆市", "上海市"}
for i := 0; i < len(lists); i++ {
if gstr.Contains(lists[i], title) {
return true
}
}
return false
}
//
//  @Title  获取IP归属地信息
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   ctx
//  @Param   ip
//  @Return  IpLocationData
//
func (component *ip) GetLocation(ctx context.Context, ip string) IpLocationData {
method, _ := g.Cfg().Get(ctx, "hotgo.ipMethod", "cz88")
if method.String() == "whois" {
return component.WhoisLocation(ctx, ip)
}
return component.Cz88Find(ctx, ip)
}

View File

@@ -0,0 +1,161 @@
//
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Author  Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package com
import (
"context"
"fmt"
"github.com/bufanyun/hotgo/app/consts"
"github.com/bufanyun/hotgo/app/model"
"github.com/dgrijalva/jwt-go"
"github.com/gogf/gf/v2/crypto/gmd5"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"time"
)
type JWT struct{}
var Jwt = new(JWT)
//
//  @Title  为指定用户生成token
//  @Description  主要用于登录成功的jwt鉴权绑定
//  @Author  Ms <133814250@qq.com>
//  @Param   ctx
//  @Param   user 用户信息
//  @Param   isRefresh 是否是刷新token
//  @Return  interface{}
//  @Return  error
//
func (component *JWT) GenerateLoginToken(ctx context.Context, user *model.Identity, isRefresh bool) (interface{}, error) {
jwtVersion, _ := g.Cfg().Get(ctx, "jwt.version", "1.0")
jwtSign, _ := g.Cfg().Get(ctx, "jwt.sign", "hotGo")
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"id": user.Id,
"username": user.Username,
"realname": user.Realname,
"avatar": user.Avatar,
"email": user.Email,
"mobile": user.Mobile,
"last_time": user.LastTime,
"last_ip": user.LastIp,
"exp": user.Exp,
"expires": user.Expires,
"app": user.App,
"role": user.Role,
"visit_count": user.VisitCount,
"is_refresh": isRefresh,
"jwt_version": jwtVersion.String(),
})
tokenString, err := token.SignedString(jwtSign.Bytes())
if err != nil {
err := gerror.New(err.Error())
return nil, err
}
tokenStringMd5 := gmd5.MustEncryptString(tokenString)
// TODO 绑定登录token
cache := Cache.New()
key := consts.RedisJwtToken + tokenStringMd5
// TODO 将有效期转为持续时间,单位:秒
expires, _ := time.ParseDuration(fmt.Sprintf("+%vs", user.Expires))
err = cache.Set(ctx, key, tokenString, expires)
if err != nil {
err := gerror.New(err.Error())
return nil, err
}
_ = cache.Set(ctx, consts.RedisJwtUserBind+user.App+":"+gconv.String(user.Id), key, expires)
return tokenString, err
}
//
//  @Title  解析token
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   tokenString
//  @Param   secret
//  @Return  jwt.MapClaims
//  @Return  error
//
func (component *JWT) ParseToken(tokenString string, secret []byte) (jwt.MapClaims, error) {
if tokenString == "" {
err := gerror.New("token 为空")
return nil, err
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return secret, nil
})
if token == nil {
err := gerror.New("token不存在")
return nil, err
}
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
return claims, nil
} else {
return nil, err
}
}
/**
token有效正确返回用户id
*/
//func(component *JWT) VerifyLoginToken(tokenString string) (uint, err error) {
// //if tokenString == "" {
// // err = gerror.New("token不能为空")
// // return 0, err
// //}
//
//}
//
//  @Title  获取 authorization
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   r
//  @Return  string
//
func (component *JWT) GetAuthorization(r *ghttp.Request) string {
// TODO 默认从请求头获取
var authorization = r.Header.Get("Authorization")
// TODO 如果请求头不存在则从get参数获取
if authorization == "" {
return r.Get("authorization").String()
}
return gstr.Replace(authorization, "Bearer ", "")
}
/**
清掉所以的相关的redis
*/
func (component *JWT) Layout(adminUserId int, tokenString string) {
if tokenString == "" {
return
}
//g.Redis().Do("HDEL", "VerifyLoginToken", gmd5.MustEncryptString(tokenString))
//// 删除
//g.Redis().Do("HDEL", "VerifyLoginTokenAdminUserId", adminUserId)
}

View File

@@ -0,0 +1,83 @@
//
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Author  Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package com
import (
"context"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/database/gredis"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
var Redis = new(redis)
type redis struct{}
//
//  @Title  实例化redis
//  @Description 
//  @Author  Ms <133814250@qq.com>
//  @Param   name
//  @Return  *gredis.Redis
//
func (component *redis) Instance(name ...string) *gredis.Redis {
return g.Redis(name...)
}
//
//  @Title  获取
//  @Description 
//  @Author  Ms <133814250@qq.com>
//  @Param   ctx
//  @Param   key
//  @Return  *gvar.Var
//  @Return  error
//
func (component *redis) Get(ctx context.Context, key string) (*gvar.Var, error) {
data, err := Redis.Instance().Do(ctx, "GET", key)
if err != nil {
err := gerror.New(err.Error())
return nil, err
}
return data, nil
}
//
//  @Title  设置
//  @Description 
//  @Author  Ms <133814250@qq.com>
//  @Param   ctx
//  @Param   key
//  @Param   value
//  @Param   expire
//  @Return  *gvar.Var
//  @Return  error
//
func (component *redis) Set(ctx context.Context, key string, value string, expire interface{}) (*gvar.Var, error) {
redisInstance := Redis.Instance()
response, err := redisInstance.Do(ctx, "SET", key, value)
if err != nil {
err := gerror.New(err.Error())
return nil, err
}
exp := gconv.Int(expire)
// TODO 设置有效期
if exp > 0 {
_, err = redisInstance.Do(ctx, "EXPIRE", key, exp)
if err != nil {
err := gerror.New(err.Error())
return nil, err
}
}
return response, nil
}

View File

@@ -0,0 +1,121 @@
//
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Author  Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package com
import (
"github.com/bufanyun/hotgo/app/consts"
"github.com/bufanyun/hotgo/app/model"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"time"
)
var Response = new(response)
type response struct{}
//
//  @Title  返回JSON数据并退出当前HTTP执行函数
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   r
//  @Param   code
//  @Param   message
//  @Param   data
//
func (component *response) JsonExit(r *ghttp.Request, code int, message string, data ...interface{}) {
component.RJson(r, code, message, data...)
r.Exit()
}
//
//  @Title  标准返回结果数据结构封装
//  @Description  返回固定数据结构的JSON
//  @Author  Ms <133814250@qq.com>
//  @Param   r
//  @Param   code 状态码(200:成功,302跳转和http请求状态码一至)
//  @Param   message 请求结果信息
//  @Param   data 请求结果,根据不同接口返回结果的数据结构不同
//
func (component *response) RJson(r *ghttp.Request, code int, message string, data ...interface{}) {
responseData := interface{}(nil)
if len(data) > 0 {
responseData = data[0]
}
Res := &model.Response{
Code: code,
Message: message,
Timestamp: time.Now().Unix(),
ReqId: Context.Get(r.Context()).ReqId,
}
// TODO 如果不是正常的返回则将data转为error
if consts.CodeOK == code {
Res.Data = responseData
} else {
Res.Error = responseData
}
// TODO 清空响应
r.Response.ClearBuffer()
// TODO 写入响应
if err := r.Response.WriteJson(Res); err != nil {
g.Log().Error(r.Context(), "响应异常:", err)
}
// TODO 加入到上下文
Context.SetResponse(r.Context(), Res)
}
//
//  @Title  返回成功JSON
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   isExit
//  @Param   r
//  @Param   message
//  @Param   data
//
func (component *response) SusJson(isExit bool, r *ghttp.Request, message string, data ...interface{}) {
if isExit {
component.JsonExit(r, consts.CodeOK, message, data...)
}
component.RJson(r, consts.CodeOK, message, data...)
}
//
//  @Title  返回失败JSON
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   isExit
//  @Param   r
//  @Param   message
//  @Param   data
//
func (component *response) FailJson(isExit bool, r *ghttp.Request, message string, data ...interface{}) {
if isExit {
component.JsonExit(r, consts.CodeNil, message, data...)
}
component.RJson(r, consts.CodeNil, message, data...)
}
//
//  @Title  重定向
//  @Description
//  @Author  Ms <133814250@qq.com>
//  @Param   r
//  @Param   location
//  @Param   code
//
func (component *response) Redirect(r *ghttp.Request, location string, code ...int) {
r.Response.RedirectTo(location, code...)
}
func (component *response) Download(r *ghttp.Request, location string, code ...int) {
r.Response.ServeFileDownload("test.txt")
}