Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ temp/
vendor
tmp/
config/settings.dev.yml
config/settings.local.yml
logs
mysql/data
redis/data
Expand Down
10 changes: 6 additions & 4 deletions apis/process/workOrder.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,16 @@ func ProcessStructure(c *gin.Context) {

// 新建工单
func CreateWorkOrder(c *gin.Context) {

err := service.CreateWorkOrder(c)
workOrderInfo, err := service.CreateWorkOrder(c)
if err != nil {
app.Error(c, -1, err, "")
app.Error(c, -1, err, "提交工单申请失败")
return
}

app.OK(c, "", "成功提交工单申请")
app.OK(c, map[string]interface{}{
"id": workOrderInfo.Id,
"biz_no": workOrderInfo.BizNo,
}, "提交工单申请成功")
}

// 工单列表
Expand Down
2 changes: 2 additions & 0 deletions config/db.sql
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ INSERT INTO `casbin_rule`(`p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (
INSERT INTO `casbin_rule`(`p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES ('p', 'admin', '/api/v1/work-order/list', 'GET', NULL, NULL, NULL);
INSERT INTO `casbin_rule`(`p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES ('p', 'admin', '/api/v1/work-order/unity', 'GET', NULL, NULL, NULL);
INSERT INTO `casbin_rule`(`p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES ('p', 'admin', '/api/v1/work-order/inversion', 'POST', NULL, NULL, NULL);
INSERT INTO `casbin_rule`(`p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES ('p', 'admin', '/api/open/work-order/create', 'POST', NULL, NULL, NULL);
INSERT INTO `casbin_rule`(`p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES ('p', 'admin', '/api/open/work-order/process-structure', 'GET', NULL, NULL, NULL);
INSERT INTO `casbin_rule`(`p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES ('p', 'admin', '/api/v1/dashboard', 'GET', NULL, NULL, NULL);
INSERT INTO `casbin_rule`(`p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES ('p', 'admin', '/api/v1/work-order/urge', 'GET', NULL, NULL, NULL);
INSERT INTO `casbin_rule`(`p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES ('p', 'admin', '/api/v1/settings', 'POST', NULL, NULL, NULL);
Expand Down
1 change: 1 addition & 0 deletions config/ferry.sql
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ CREATE TABLE `p_work_order_info` (
`title` varchar(128) DEFAULT NULL,
`priority` int DEFAULT NULL,
`process` int DEFAULT NULL,
`biz_no` varchar(128) DEFAULT NULL,
`classify` int DEFAULT NULL,
`is_end` int DEFAULT '0',
`is_denied` int DEFAULT '0',
Expand Down
11 changes: 11 additions & 0 deletions config/settings.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,21 @@ settings:
maxbackups: 300
maxsize: 10240
path: ./logs/ferry.log
openauth:
clients:
ferry:
identitykey: 1
rolekey: admin
secret: test2026
nonceexpiretime: 600
timeout: 300
public:
islocation: 0
redis:
url: redis://:123456@192.168.31.94:6379
addr: 192.168.31.94:6379
db: 0
pwd: "123456"
ssl:
key: keystring
pem: temp/pem.pem
15 changes: 13 additions & 2 deletions config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ settings:
password: 123456
port: 3306
username: root
redis:
url: redis://ferry_redis:6379
addr: ferry_redis:6379
db: 0
pwd: ""
dingtalk:
agentid: 1234567890
appkey: your dingtalk appkey
Expand Down Expand Up @@ -59,8 +64,14 @@ settings:
path: ./logs/ferry.log
public:
islocation: 0
redis:
url: redis://ferry_redis:6379
ssl:
key: keystring
pem: temp/pem.pem
openauth:
appkeys:
ferry:
secret: 123456
identityKey: 1
roleKey: admin
nonceexpiretime: 600
timeout: 300
177 changes: 177 additions & 0 deletions middleware/open_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package middleware

import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
jwt "ferry/pkg/jwtauth"
"ferry/pkg/logger"
"ferry/pkg/redis"
"ferry/tools"
"ferry/tools/config"
"net/http"
"strconv"
"strings"
"time"

"github.com/gin-gonic/gin"
)

// OpenAuth API认证中间件
func OpenAuth() gin.HandlerFunc {
return func(c *gin.Context) {
// 1. 从请求中获取认证参数 - 使用更合适的命名
appKey := c.Request.Header.Get("X-App-Key")
signature := c.Request.Header.Get("X-Signature")
timestampStr := c.Request.Header.Get("X-Timestamp")
nonce := c.Request.Header.Get("X-Nonce")

// 2. 验证参数是否完整
if appKey == "" || signature == "" || timestampStr == "" || nonce == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"msg": "缺少必要的认证参数",
})
c.Abort()
return
}

// 3. 验证时间戳是否有效
timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"msg": "无效的时间戳格式",
})
c.Abort()
return
}

currentTime := time.Now().Unix()
if currentTime-timestamp > config.OpenAuthConfig.Timeout || timestamp-currentTime > config.OpenAuthConfig.Timeout {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"msg": "时间戳已过期或无效",
})
c.Abort()
return
}

// 4. 验证nonce是否已被使用
exists, err := checkNonce(nonce)
if err != nil {
logger.Error(err)
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "验证nonce失败",
})
c.Abort()
return
}

if exists {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"msg": "nonce已被使用",
})
c.Abort()
return
}

// 记录nonce到Redis,设置过期时间
expireTime := time.Duration(config.OpenAuthConfig.NonceExpireTime) * time.Second
err = setNonce(nonce, expireTime)
if err != nil {
logger.Error(err)
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "存储nonce失败",
})
c.Abort()
return
}

// 5. 从配置中获取AppSecret
appConfig, exists := config.OpenAuthConfig.Clients[appKey]
if !exists || appConfig == nil || appConfig.Secret == "" || appConfig.IdentityKey == 0 || appConfig.RoleKey == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"msg": "无效的AppKey",
})
c.Abort()
return
}

// 6. 验证签名是否正确
validSign, err := generateSign(appKey, appConfig.Secret, timestampStr, nonce, c)
if err != nil {
logger.Error(err)
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "生成签名失败",
})
c.Abort()
return
}

if signature != validSign {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"msg": "无效的签名",
})
c.Abort()
return
}
//设置用户ID
claims := jwt.MapClaims{
jwt.IdentityKey: float64(appConfig.IdentityKey),
jwt.RoleKey: appConfig.RoleKey,
}
c.Set("JWT_PAYLOAD", claims)
// 认证通过,继续处理请求
c.Next()
}
}

// 签名算法:HMAC-SHA256(AppSecret, AppKey + timestamp + nonce + requestBody)
func generateSign(appKey, appSecret, timestamp, nonce string, c *gin.Context) (string, error) {
// 获取请求体
body, err := tools.GetBodyString2(c)
if err != nil {
return "", err
}

// 组合签名字符串
// 按照字典序排序参数
params := []string{appKey, timestamp, nonce, body}
//sort.Strings(params)
signStr := strings.Join(params, "")

// 使用HMAC-SHA256算法生成签名
hmacHash := hmac.New(sha256.New, []byte(appSecret))
_, err = hmacHash.Write([]byte(signStr))
if err != nil {
return "", err
}

// 将签名转换为十六进制字符串
signature := hex.EncodeToString(hmacHash.Sum(nil))

return signature, nil
}

// setNonce 将nonce存储到Redis中
func setNonce(nonce string, expireTime time.Duration) error {
key := "workorder:openauth:nonce:" + nonce
return redis.GetClient().Set(key, time.Now().Unix(), expireTime).Err()
}

// checkNonce 检查nonce是否已存在
func checkNonce(nonce string) (bool, error) {
key := "workorder:openauth:nonce:" + nonce
exists, err := redis.GetClient().Exists(key).Result()
if err != nil {
return false, err
}
return exists > 0, nil
}
4 changes: 2 additions & 2 deletions middleware/permission.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/gin-gonic/gin"
)

//权限检查中间件
// 权限检查中间件
func AuthCheckRole() gin.HandlerFunc {
return func(c *gin.Context) {
data, _ := c.Get("JWT_PAYLOAD")
Expand All @@ -21,7 +21,7 @@ func AuthCheckRole() gin.HandlerFunc {
tools.HasError(err, "", 500)
//检查权限
res, err := e.Enforce(v["rolekey"], c.Request.URL.Path, c.Request.Method)
logger.Info(v["rolekey"], c.Request.URL.Path, c.Request.Method)
logger.Info(v["rolekey"], c.Request.URL.Path, " ", c.Request.Method)
tools.HasError(err, "", 500)

if res {
Expand Down
1 change: 1 addition & 0 deletions models/process/workOrder.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type WorkOrderInfo struct {
Title string `gorm:"column:title; type:varchar(128)" json:"title" form:"title"` // 工单标题
Priority int `gorm:"column:priority; type:int(11)" json:"priority" form:"priority"` // 工单优先级 1,正常 2,紧急 3,非常紧急
Process int `gorm:"column:process; type:int(11)" json:"process" form:"process"` // 流程ID
BizNo string `gorm:"column:biz_no; type:varchar(128)" json:"biz_no" form:"biz_no"` // 业务ID
Classify int `gorm:"column:classify; type:int(11)" json:"classify" form:"classify"` // 分类ID
IsEnd int `gorm:"column:is_end; type:int(11); default:0" json:"is_end" form:"is_end"` // 是否结束, 0 未结束,1 已结束
IsDenied int `gorm:"column:is_denied; type:int(11); default:0" json:"is_denied" form:"is_denied"` // 是否被拒绝, 0 没有,1 有
Expand Down
25 changes: 10 additions & 15 deletions pkg/notify/dingtalk/dingtalk.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ package dingtalk
import (
"bytes"
"encoding/json"
"ferry/pkg/logger"
"ferry/pkg/redis"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
"ferry/pkg/logger"

"github.com/go-redis/redis"
"github.com/spf13/viper"
)

// var appKey = viper.GetString("settings.dingtalk.appkey")
// var appSecret = viper.GetString("settings.dingtalk.appsecret")
// var agentId = viper.GetInt64("settings.dingtalk.agentid")
// var appSecret = viper.GetString("settings.dingtalk.appsecret")
// var agentId = viper.GetInt64("settings.dingtalk.agentid")

const (
GetAccessTokenUrl = "https://oapi.dingtalk.com/gettoken"
Expand Down Expand Up @@ -140,23 +140,18 @@ func SendFlowMsg(token string, content SendFlowMsgBody) error {
}

func SendDingMsg(phoneList []string, url string, msgTitle string, msgCreator string, priority string, createdAt string) {
appKey := viper.GetString("settings.dingtalk.appkey")
appSecret := viper.GetString("settings.dingtalk.appsecret")
agentId := viper.GetInt64("settings.dingtalk.agentid")

client := redis.NewClient(&redis.Options{
Addr: viper.GetString("settings.redis.host"),
Password: viper.GetString("settings.redis.pwd"),
DB: 1, // use default DB
})
appKey := viper.GetString("settings.dingtalk.appkey")
appSecret := viper.GetString("settings.dingtalk.appsecret")
agentId := viper.GetInt64("settings.dingtalk.agentid")

client := redis.GetClient()
var token string
tokenByte, err := client.Get("accessTokenDingtalk").Result()
tokenByte, err := client.Get("workorder:accessTokenDingtalk").Result()

if err != nil {
logger.Info(err)
token = GetAccessToken(appKey, appSecret)
err = client.Set("accessTokenDingtalk", token, 120*time.Minute).Err()
err = client.Set("workorder:accessTokenDingtalk", token, 120*time.Minute).Err()
if err != nil {
logger.Info(err)
}
Expand Down
Loading