跳到主要内容

about

os-w项目是采用go开发,本文将os-w的初期开发时的代码分享出来,供有需要的同学拿来参照。 主要有如下几部分

  • 服务端
    • 多并发
    • 开启webflow(供前导访问)
    • 开启命令行管理服务端
    • 支持配置文件和license授权文件
    • bit流加密
  • 客户端 超时退出
  • 工具 产生license文件,查验sr运行环境等
# tree osw
osw
├── client
│   ├── client.go # 客户端
│   ├── go.mod
│   └── go.sum
├── readme.md
├── sr
│   ├── conf.yaml # 配置文件
│   ├── go.mod
│   ├── go.sum
│   ├── license.txt # license文件,通过token.go来产生
│   └── sr.go # 主服务程序
└── tools
├── getuuid.go # 返回uuid
├── go.mod
├── go.sum
└── token.go # license产生与校验

服务端

sr.go

sr.go
package main

import (
"bufio"
"bytes"
"errors"
"fmt"
"log"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"

//"reflect"
"runtime"
"strings"
"time"

_ "github.com/codyguo/godaemon"
"github.com/dgrijalva/jwt-go"
"gopkg.in/yaml.v2"
)

//配置文件内容的数据结构
type Conf struct {
App string
Ver string
Limitnum int
Secretkey string
Service map[string](map[string]string)
}
var serviceConf Conf


// license信息数据结构。
type CustomClaims struct {
UserId int64
UserName string
UserEmail string
UserMobile string
NetworkIP string
NetworkMAC string
HostName string
UUID string
jwt.StandardClaims
}
var isLicense bool = true
var enableLicense chan bool //若在查验license时,license失败,则会: enableLicense <- false

// 全局变量定义
var limitGoroutine chan int
var LimitNum int //定义最大并发数量
var logNewInfo string //新的日志信息


// "."表示当前主程序所在的目录,".\\yaml"表示当前目录下的子目录yaml。注意”\\“是转义方式表达,是一个”\“
const logPath = "."

// 全局常量定义
const (
flagLogin_user = "LOGIN_USER" //返回给客户端的标识,表示处于用户名称录入阶段。
flagLogin_pwd = "LOGIN_PASSWORD" //返回给客户端的标识,表示处于用户密码录入阶段。
flagLogin_accpet_True = "LOGIN_TRUE" //返回给客户端的标识,c_GuoFS_USER_TRUE表示用户和密码正确,
flagLogin_accpet_False = "LOGIN_FALSE" //返回给客户端的标识,c_GuoFS_USER_FALSE表示不正确。
)

//---------------------------主协程-------------------------------
func main() {
//解析配置文件conf.yaml
getConf()
LimitNum = serviceConf.Limitnum
mgmPort := (serviceConf.Service)["mgm"]["port"]

//校验license是否有效(首次)
licenseInfo,flags := GetLicense()
fmt.Println(licenseInfo)
isLicense = flags
if !(isLicense) {
fmt.Println("license失效后,系统不能正常启动,请及时联系管理员.")
return
}
//初始化enableLicense通常为true
enableLicense = make(chan bool)
//enableLicense <- true

//采用通道方式来控制并发数
limitGoroutine = make(chan int, LimitNum) //最大并发数为LimitNum+1。采用channel栈方式来控制最大并发数量

//----------------服务端操作------------------------
//服务端建立侦听
var err error
var sr net.Listener
sr, err = net.Listen("tcp", ":"+mgmPort)
if err != nil {
log.Fatalln(err)
}
defer sr.Close()
log.Println("服务启动成功!")
//log.Println("sr变量类型:", reflect.TypeOf(sr))

//子协程
go pxeWeb() //业务流(http API)
go moreProcess(sr) //管理程序

//主程序等待(少占CPU资源)
for {
select {
case <-enableLicense:
fmt.Println("授权已失效或运行环境不匹配,服务退出,请联系管理员")
return
case <-time.After(time.Second):
//fmt.Println("主协程等待")
time.Sleep(time.Second)
default:
time.Sleep(time.Second)
}
}
}

//---------------------------读取外部配置文件内容-------------------------------
func getConf() {
//打开配置文件
filename := "./conf.yaml"
file, err := os.Open(filename)
if err != nil {
fmt.Println(err)
return
}
defer file.Close()

//读取配置文件
w := new(bytes.Buffer)
w.ReadFrom(file)
//fmt.Printf("%#v\n\n",w.String())
//var serviceConf Conf
err = yaml.Unmarshal(w.Bytes(), &serviceConf)
if err != nil {
fmt.Println("格式化错误")
fmt.Println(err.Error())
return
}
//fmt.Printf("%+v\n\n", serviceConf)
//fmt.Printf("secretkey:%v\n",serviceConf.Secretkey)
//fmt.Printf("mgm :%v\n",(serviceConf.Service)["mgm"])
//fmt.Printf("mgm ip:%v\n",(serviceConf.Service)["mgm"]["ip"])
//fmt.Printf("mgm port:%v\n",(serviceConf.Service)["mgm"]["port"])
}
//---------------------------cmdline 管理-------------------------------
//负责多协程开启。
func moreProcess(sr net.Listener){
var err error
for {
//用户端连接
var conn net.Conn
conn, err = sr.Accept() //在没有client连接时,在此等待。只有client连接后,其后面的代码才会运行。
if err != nil {
log.Println("子进程意外中止1,", err) //日志打印到屏幕
log.Printf("最大并发进程:%d,当前已有:%d",LimitNum,len(limitGoroutine))
//
save_ErrorLog("子进程意外中止1," + err.Error())
continue //跳出当前循环,继续下一个循环。
}
//若Accept成功,则通道进栈值1
limitGoroutine <- 1
go adminServer(conn)
log.Printf("最大并发进程:%d,当前已有:%d",LimitNum,len(limitGoroutine))

//
logNewInfo = fmt.Sprintf("最大并发进程:%d,当前已有:%d",LimitNum,len(limitGoroutine))
save_ErrorLog(logNewInfo)
}
}


//单次:service与client会话
func adminServer(conn net.Conn){
defer conn.Close()

//定义变量
var (
buf = make([]byte, 1024) //从客户端读取数据
count int //读取的字节数量
err error //定义err
userInfo = map[string]string{
"user": "afed5ab718a01325481e742f6622dc132a54a8a14c876218bcf95d5d5fd6f3c2", //guofs,采用sha256码
"passwd": "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", //123456
}
cmdList = map[string]([]byte){
"help": []byte(`
系统有如下常用命令
-----------------------------------
help 查看帮助
license 查验license
list 命令列表
ver 查看软件版本
exit 退出
`),
"ver": []byte("0.1.6"),
}
)

//验证用户帐号
for {
//提示用户录入用户名称,并读取
conn.Write([]byte(flagLogin_user))
count, err = conn.Read(buf) //在没有读取数据时,在此等待。只有读取数据后,其后面的代码才会运行。
//若从client端读取有误,则中止进
if err != nil {
log.Println("子进程意外中止2,", err) //日志打印到屏幕
buf=nil
<- limitGoroutine //清空1个栈位。
return
}
userName := strings.Trim(string(buf[:count]), "\n") //buf[:count]是关键。提取出有效长度。

//提示用户录入用户密码并提取
conn.Write([]byte(flagLogin_pwd))
count, err = conn.Read(buf)
if err != nil {
log.Println("子进程意外中止3,", err) //日志打印到屏幕
buf=nil
<- limitGoroutine //清空1个栈位。
return
}
userPwd := strings.Trim(string(buf[:count]), "\n")

//验证用户,若不正确,请重新录入。若正确,进入业务处理部分。区分大写
if !((strings.Compare(userName,userInfo["user"]) == 0 ) && (strings.Compare(userPwd,userInfo["passwd"]) == 0)) {
conn.Write([]byte(flagLogin_accpet_False))
} else{
conn.Write([]byte(flagLogin_accpet_True))
break
}
}



//业务处理部分
for {
//从client端读取数据
count, err = conn.Read(buf) //在没有读取数据时,在此等待。只有读取数据后,其后面的代码才会运行。
//若从client端读取有误,则中止进
if err != nil {
log.Println("子进程意外中止2,", err) //日志打印到屏幕
buf=nil
<- limitGoroutine //清空1个栈位。
return
}
//取得客户端写入信息。
userInputContext := strings.Trim(string(buf[:count]), "\n") //buf[:count]是关键。提取出有效长度。

switch userInputContext {
case "exit": ///当用户录入exit时,退出服务
conn.Write([]byte("ok,bye~~,see you\n\n"))
<- limitGoroutine
return
case "help": //可以定义更丰富的命令,命令的输出,可采用函数直接给命令的输出赋值
conn.Write(cmdList["help"])
case "license":
licensestr,_ := GetLicense()
conn.Write([]byte(licensestr))
case "ver":
cmdList["ver"] = []byte("stable : v0.1.6\n")
conn.Write(cmdList["ver"])
default:
logNewInfo = fmt.Sprintf("客户端:%v,接收时间:%v,接收内容:%+v,字节数:%d\n", conn.RemoteAddr().String(),time.Now().Format(time.StampNano),userInputContext,len(userInputContext)) //回显示出来结果
save_ErrorLog(logNewInfo)
fmt.Println(logNewInfo)
conn.Write([]byte("thks,I got msg:" + userInputContext + "\n"))
}
}

}

//---------------------------自定义日志-------------------------------
// 日志写入
func log_Write(filePath string, logContext []string) {
//设置日志文件路径和名称
curFile, _ := exec.LookPath(os.Args[0])
logFile := filePath + string(os.PathSeparator) + filepath.Base(curFile) + "_" + time.Now().Format(time.DateOnly) + ".log"
//fmt.Println(logFile)

//打开日志文件,若不存在,就新建,若存在,就追加。
objLogFile, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
fmt.Println(err)
return
}
defer objLogFile.Close()
//objLogFile.Write([]byte("test"))
for _, v := range logContext {
//logTxt := time.Now().Format(time.DateTime) + ";" + v + "\n"
write := bufio.NewWriter(objLogFile) //写入文件时,使用带缓存的 *Writer
write.WriteString(v)
write.Flush()
}
}

// 保存程序运行过程中的日志
func save_ErrorLog(err string) {
logTxt := time.Now().Format(time.DateTime) + err + "\n"
log_Write(logPath, []string{logTxt}) //日志写入日志文件
}

//---------------------------license-------------------------------
//返回操作系统的UUID
func GetSystemUUID() string {
osType := runtime.GOOS
if osType == "windows" {
cmd := exec.Command("wmic", "csproduct", "get","UUID")
out, err := cmd.Output()
if err != nil {
fmt.Println(err)
return "null"
}
strOut := string(out)
//fmt.Printf("%#v\n",strOut)
strOut = strings.TrimSpace(strOut)
strOut = strings.Trim(strOut,"\r\r\n")
//for i,v := range strings.Split(strOut,"\n") {
// fmt.Printf("key=%v,%#v\n",i,v)
//}
uuid := strings.Split(strOut,"\n")[1]
return uuid
} else if osType == "linux" {
cmd := exec.Command("dmidecode", "-s", "system-uuid")
out, err := cmd.Output()
if err != nil {
fmt.Println(err)
return "null"
}
//fmt.Println(strOut)
strOut := string(out)
strOut = strings.Trim(strOut,"\n")
//fmt.Printf("%#v\n",strOut)
uuid := strOut
return uuid
}
return "other"
}

// 解析token
func ParseToken(tokenString string, key []byte) (*CustomClaims, error) {
/*
jwt.ParseWithClaims有三个参数
第一个就是加密后的token字符串
第二个是Claims, &CustomClaims 传指针的目的是。然让它在我这个里面写数据
第三个是一个自带的回调函数,将秘钥和错误return出来即可,要通过密钥解密
*/
//定义解析后存储信息的变量,采用指针方式,可以将内部数据直接返回到外部。
CustomClaims_Context := &CustomClaims{}
//解析
_, err := jwt.ParseWithClaims(
tokenString,
CustomClaims_Context,
func(token *jwt.Token) (interface{}, error) {
return []byte(key), nil
},
)
if err != nil {
return nil, err
}

//只有解析出的uuid与当前即时从系统取到uuid完全相同时,才可以
if strings.EqualFold(CustomClaims_Context.UUID, GetSystemUUID()) {
return CustomClaims_Context, nil
} else {
err = errors.New("UUID不匹配")
return nil, err
}
//return CustomClaims_Context, nil
}

//校证和解析license文件
func GetLicense() (string, bool) {
var secretKey = []byte(serviceConf.Secretkey)
//打开文件
var snFile = "./license.txt"
snfile, err := os.OpenFile(snFile, os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
//fmt.Println("文件打开失败 = ", err)
snfile.Close()
return "license.txt文件打开失败\n", false
}
defer snfile.Close()
w := new(bytes.Buffer)
w.ReadFrom(snfile)
token_cipher_context := w.String()
tokenstr,err:=ParseToken(token_cipher_context,secretKey)
if err!=nil {
//fmt.Println("失效")
return "授权license文件已失效,或与当前环境不匹配.\n", false
}
//fmt.Printf("解析后的token:\n%+v\n\n",*tokenstr)
//fmt.Println("当前License有效。\n")
//fmt.Println("用户:",tokenstr.UserName)
//fmt.Println("电话:",tokenstr.UserMobile)
//fmt.Println("邮箱:",tokenstr.UserEmail)
//fmt.Println("项目:",tokenstr.Subject)
//fmt.Println("UUID:",tokenstr.UUID)
dataTimeStr := time.Unix(tokenstr.ExpiresAt, 0).Format("2006-01-02 15:04:05")
//fmt.Println("有效期:",dataTimeStr)
reStr := fmt.Sprintf("当前License有效.\n有效期:%v\n\n用户:%v\n电话:%v\n邮箱:%v\n项目:%v\nUUID:%v\n",
dataTimeStr,tokenstr.UserName,tokenstr.UserMobile,tokenstr.UserEmail,tokenstr.Subject,tokenstr.UUID)
//fmt.Println(reStr)
return reStr, true
}


//定时器,每1分钟查检一次license是否有效。
func checkLicense() {
for {
time.Sleep(time.Second * 60)
_,isLicense = GetLicense()
if !(isLicense) {
enableLicense <- false
}
}
}

//---------------------------(pxe)http API-------------------------------
func pxeWeb(){

go checkLicense()

if isLicense {
http.HandleFunc("/tp1", test1)
http.HandleFunc("/tp2", test2)
//log.Fatal(http.ListenAndServe("localhost:8080", nil))
webPort := (serviceConf.Service)["webflow"]["port"]
if err:=http.ListenAndServe(":"+webPort, nil);err != nil{
log.Fatalln(err)
}
}
}
func test1(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "C语言中文网\n")
}
func test2(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "go1语言中文网\n")
w.Write([]byte("go2语言中文网\n"))
}

配置文件

app: api
ver: 0.1.6
limitnum: 5
secretkey: eaba903d7c5e8f88159bfd4379d167ab
service:
mgm:
ip: 0.0.0.0
port: 1234
webflow:
ip: 0.0.0.0
port: 8088
mysql:
ip: 127.0.0.1
port: 3306
dbuser: root
dbpwd: 123456
dbname: os-w

license文件

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjExMCwiVXNlck5hbWUiOiLpg63msI_pm4blm6IiLCJVc2VyRW1haWwiOiJndW9mc0AxMzkuY29tIiwiVXNlck1vYmlsZSI6IjEzNyoqKiowODA2IiwiTmV0d29ya0lQIjoiMTkyLjE2OC4zLjExMCIsIk5ldHdvcmtNQUMiOiJGMC1ERS1GMS04Qy1CNS1GMiIsIkhvc3ROYW1lIjoiRlMwMDctRGFycnkiLCJVVUlEIjoiQTVCRTg1RTAtMzQ1MC0xMUUxLUI2OTgtODkwOTQ2MEQwMjkxIiwiYXVkIjoib3MtdyIsImV4cCI6MjAyMDc4MjI5NCwiaWF0IjoxNzA1NDIyMjk0LCJpc3MiOiJvcy13Iiwic3ViIjoib3Mtd-mhueebriJ9.FoIIKdaRG1eyDyOiJe3sVLspQqorNsf-FfyfBf_g7jY

客户端

client.go
package main

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"log"
"net"
"os"
"strings"
"time"

"golang.org/x/term"
)

//提示符
const PROMAT = "os-w # "

//定义变量
var (
buf = make([]byte, 2048)
count int
sysReturnContext string
strInput string
err error
isTimeOut bool //是否超时的标识
)


func main() {
//建立连接
conn, err := net.DialTimeout("tcp", "192.168.3.110:1234", time.Second*10)
if err != nil {
log.Fatalln(err)
}
defer conn.Close()
log.Println("连接成功")
fmt.Print("Hello,welcome os-w\nver 0.1.6\n\n")

//超时查验
go timeout(conn)

//进入循环体,对来自服务器的返回进行解析。
checkLogin(conn)

//业务处理
saleData(conn)
}

//用户登录
func checkLogin(conn net.Conn){
//用户验证
for {
//从sr读取信息
count, err = conn.Read(buf)
if err != nil {
break
}
sysReturnContext = string(buf[:count])
//fmt.Println(sysReturnContext)
switch sysReturnContext {
case "LOGIN_USER":
fmt.Print("User : ")
isTimeOut = true
fmt.Scanln(&strInput)
userName := strings.Trim(string(strInput), "\n") //去掉录入的回车
userName = strings.Trim(string(userName), " ") //去掉尾部多余的空格
if len(userName) == 0 {
conn.Write([]byte("user"))
}
conn.Write([]byte(sha256_str(userName)))
isTimeOut = false
case "LOGIN_PASSWORD":
fmt.Print("Password : ")
isTimeOut = true
tmp, _ := term.ReadPassword(int(os.Stdin.Fd()))
passwd := strings.Trim(string(tmp), "\n")
passwd = strings.Trim(string(passwd), " ")
if len(passwd) == 0 { //空密码录入时的处理,即直接回车,也没有空格。当为空时,会出错,类似死循环。
conn.Write([]byte("Password"))
}
conn.Write([]byte(sha256_str(passwd)))
isTimeOut = false
case "LOGIN_FALSE":
fmt.Print("\n用户名称或密码不正确,请重新录入。\n\n")
case "LOGIN_TRUE":
fmt.Print("\n欢迎你,若需帮助,可输入help。\n\n")
return
//case "LOGIN_TRUE": // 在此不使用switch方式,因为break时只是跳到当前switch,而不能跳出当前for.
//fmt.Print("\n欢迎你,若需帮助,可输入help。\n")
//break
}
//if sysReturnContext == "LOGIN_TRUE" { //采用if比较后,break可以跳出当前for
// fmt.Print("\n欢迎你,若需帮助,可输入help。\n\n")
// return
//}
}
}

//业务处理
func saleData(conn net.Conn){
for{
fmt.Print(PROMAT)
strInput =""
isTimeOut = true
fmt.Scanln(&strInput)
strInput = strings.Trim(string(strInput), "\n")
strInput = strings.Trim(string(strInput), " ")
if len(strInput) == 0 {
fmt.Print("不能为空,请重新录入命令\n")
continue
}
//向连接写入健盘输入数据
conn.Write([]byte(strInput))
isTimeOut = false //并发协程定中的定时器会定时查看此值,若为true,则强制退出。

//从sr读取反馈回来的信息
count, err = conn.Read(buf)
if err != nil {
break
}
sysReturnContext = string(buf[:count])
//fmt.Println("服务器回应 : ",sysReturnContext)
fmt.Println(sysReturnContext)
// 退出client
if strings.EqualFold(strInput,"exit") {
return
}
//及时清空strInput字串
//strInput = ""
}
}

//超时判断,若客户端超时,会强制退出。
//定时器,每xxxx秒查一下用户是否有录入,若没有,就退出。
//此类超时程序,曾在服务端测试,效果不理想,在client端最好。
func timeout(conn net.Conn) {
for {
time.Sleep(time.Hour * 24)
if isTimeOut {
fmt.Print("\n\n超时24h,强制退出\nbye\n\n")
conn.Write([]byte("exit"))
conn.Close()
return
}
}
}

//sha256
func sha256_str(str string) string {
hash := sha256.Sum256([]byte(str))
return hex.EncodeToString(hash[:])
}

工具

token.go

package main

import (
"bytes"
"errors"
"fmt"
"log"
"os"
"os/exec"
"runtime"
"strings"
"time"

"github.com/dgrijalva/jwt-go"
)

// 自定义类,如用户信息、浏览器信息等都可以存储在结构体中。
type CustomClaims struct {
UserId int64
UserName string
UserEmail string
UserMobile string
NetworkIP string
NetworkMAC string
HostName string
UUID string
jwt.StandardClaims
}

func main() {
// 此常量需保密,(理想情况下,该常量将存储在代码库外部),需要32个字节
//var secretKey = generateRandomString(32)
var secretKey = []byte("eaba903d7c5e8f88159bfd4379d167ab")
//fmt.Printf("key:%v\nLen:%d\n",secretKey,len(secretKey))


//定义token信息,结构可自定义
customClaims := &CustomClaims{
UserId: 110,
UserName: "xxx集团-智信部",
UserEmail: "guofs@139.com",
UserMobile: "137****0806",
NetworkIP: "192.168.3.110",
NetworkMAC: "F0-DE-F1-8C-B5-F2",
HostName: "FS007-Darry",
UUID: "A5BE85E0-3450-11E1-B698-8909460D0291",
StandardClaims: jwt.StandardClaims{
Audience: "os-w", //颁发给谁,就是使用的一方
ExpiresAt: time.Now().Add(time.Hour * 24 * 365 * 10).Unix(), //过期时间
//Id: "", //非必填
IssuedAt: time.Now().Unix(), //颁发时间
Issuer: "os-w", //颁发者
//NotBefore: 0, //生效时间,就是在这个时间前不能使用,非必填
Subject: "os-w项目",
},
}
//创建Token,指时需手工指定UUID.
token_cipher_context,_ := CreateToken(customClaims,secretKey)
fmt.Printf("token密文:\n%v\n\n",token_cipher_context)
//写入文件
var filename = "./license.txt"
file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Println("文件打开失败 = ", err)
file.Close()
return
}
//defer file.Close()
os.Truncate(filename, 0)
file.WriteString(token_cipher_context)
file.Close()
//解析Token。从Token中解析出的UUID,与解析过程中从当前系统中读取的UUID值相比较,相同时才可以。
//打开文件
var snFile = "./license.txt"
snfile, err := os.OpenFile(snFile, os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Println("文件打开失败 = ", err)
snfile.Close()
return
}
defer snfile.Close()
w := new(bytes.Buffer)
w.ReadFrom(snfile)
token_cipher_context = w.String()
tokenstr,err:=ParseToken(token_cipher_context,secretKey)
if err!=nil {
//fmt.Println("失效")
log.Fatalln(err)
return
}
fmt.Printf("解析后的token:\n%+v\n\n",*tokenstr)
fmt.Println("当前License有效。\n")

fmt.Println("用户:",tokenstr.UserName)
fmt.Println("电话:",tokenstr.UserMobile)
fmt.Println("邮箱:",tokenstr.UserEmail)
fmt.Println("项目:",tokenstr.Subject)
dataTimeStr := time.Unix(tokenstr.ExpiresAt, 0).Format("2006-01-02 15:04:05")
fmt.Println("有效期:",dataTimeStr)
}

//创建token:办法
func CreateToken(tokenClaims *CustomClaims, key []byte) (string, error) {
//将token信息编码为token码
token := jwt.New(jwt.SigningMethodHS256)
token.Claims = *tokenClaims
tokenString, err := token.SignedString([]byte(key))
if err != nil {
return "", err
}
/*
defer func() {
e := recover()
if e != nil {
panic(e)
}
}()
*/
return tokenString, nil
}

// 解析token
func ParseToken(tokenString string, key []byte) (*CustomClaims, error) {
/*
jwt.ParseWithClaims有三个参数
第一个就是加密后的token字符串
第二个是Claims, &CustomClaims 传指针的目的是。然让它在我这个里面写数据
第三个是一个自带的回调函数,将秘钥和错误return出来即可,要通过密钥解密
*/
//定义解析后存储信息的变量,采用指针方式,可以将内部数据直接返回到外部。
CustomClaims_Context := &CustomClaims{}
//解析
_, err := jwt.ParseWithClaims(
tokenString,
CustomClaims_Context,
func(token *jwt.Token) (interface{}, error) {
return []byte(key), nil
},
)
if err != nil {
return nil, err
}
//只有解析出的uuid与当前即时从系统取到uuid完全相同时,才可以
if strings.EqualFold(CustomClaims_Context.UUID, GetSystemUUID()) {
return CustomClaims_Context, nil
} else {
err = errors.New("UUID不匹配")
return nil, err
}
//return CustomClaims_Context, nil
}


//返回操作系统的UUID
func GetSystemUUID() string {
osType := runtime.GOOS
if osType == "windows" {
cmd := exec.Command("wmic", "csproduct", "get","UUID")
out, err := cmd.Output()
if err != nil {
fmt.Println(err)
return "null"
}
strOut := string(out)
//fmt.Printf("%#v\n",strOut)
strOut = strings.TrimSpace(strOut)
strOut = strings.Trim(strOut,"\r\r\n")
//for i,v := range strings.Split(strOut,"\n") {
// fmt.Printf("key=%v,%#v\n",i,v)
//}
uuid := strings.Split(strOut,"\n")[1]
return uuid
} else if osType == "linux" {
cmd := exec.Command("dmidecode", "-s", "system-uuid")
out, err := cmd.Output()
if err != nil {
fmt.Println(err)
return "null"
}
//fmt.Println(strOut)
strOut := string(out)
strOut = strings.Trim(strOut,"\n")
//fmt.Printf("%#v\n",strOut)
uuid := strOut
return uuid
}
return "other"
}

getuuid.go

package main

import (
"fmt"
"os/exec"
"runtime"
"strings"
)

func main() {
fmt.Println(GetSystemUUID())
}

//返回操作系统的UUID
func GetSystemUUID() string {
osType := runtime.GOOS
if osType == "windows" {
cmd := exec.Command("wmic", "csproduct", "get","UUID")
out, err := cmd.Output()
if err != nil {
fmt.Println(err)
return "null"
}
strOut := string(out)
//fmt.Printf("%#v\n",strOut)
strOut = strings.TrimSpace(strOut)
strOut = strings.Trim(strOut,"\r\r\n")
//for i,v := range strings.Split(strOut,"\n") {
// fmt.Printf("key=%v,%#v\n",i,v)
//}
uuid := strings.Split(strOut,"\n")[1]
return uuid
} else if osType == "linux" {
cmd := exec.Command("dmidecode", "-s", "system-uuid")
out, err := cmd.Output()
if err != nil {
fmt.Println(err)
return "null"
}
//fmt.Println(strOut)
strOut := string(out)
strOut = strings.Trim(strOut,"\n")
//fmt.Printf("%#v\n",strOut)
uuid := strOut
return uuid
}
return "other"
}

测试

  1. step1
    查看uuid
# go run getuuid.go
A5BE85E0-3450-11E1-B698-8909460D0291
  1. step2
    产生license文件
# go run token.go
token密文:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjExMCwiVXNlck5hbWUiOiLpg63msI_pm4blm6IiLCJVc2VyRW1haWwiOiJndW9mc0AxMzkuY29tIiwiVXNlck1vYmlsZSI6IjEzNyoqKiowODA2IiwiTmV0d29ya0lQIjoiMTkyLjE2OC4zLjExMCIsIk5ldHdvcmtNQUMiOiJGMC1ERS1GMS04Qy1CNS1GMiIsIkhvc3ROYW1lIjoiRlMwMDctRGFycnkiLCJVVUlEIjoiQTVCRTg1RTAtMzQ1MC0xMUUxLUI2OTgtODkwOTQ2MEQwMjkxIiwiYXVkIjoib3MtdyIsImV4cCI6MjAyMDc4MjI5NCwiaWF0IjoxNzA1NDIyMjk0LCJpc3MiOiJvcy13Iiwic3ViIjoib3Mtd-mhueebriJ9.FoIIKdaRG1eyDyOiJe3sVLspQqorNsf-FfyfBf_g7jY

当前License有效。
用户: xxx集团-智信部
电话: 137****0806
邮箱: guofs@139.com
项目: os-w项目
有效期: 2034-01-14 00:24:54
  1. 运行sr端
# go run .\sr.go
当前License有效.
有效期:2034-01-14 00:24:54

用户:郭氏集团
电话:137****0806
邮箱:guofs@139.com
项目:os-w项目
UUID:A5BE85E0-3450-11E1-B698-8909460D0291

2024/01/17 00:25:55 服务启动成功!
2024/01/17 00:27:11 最大并发进程:5,当前已有:1
  1. 客户端
# go run client.go
2024/01/17 00:27:11 连接成功
Hello,welcome os-w
ver 0.1.6

User : guofs
Password :
欢迎你,若需帮助,可输入help。

os-w # help

系统有如下常用命令
-----------------------------------
help 查看帮助
license 查验license
list 命令列表
ver 查看软件版本
exit 退出

os-w # license
当前License有效.
有效期:2034-01-14 00:24:54

用户:xxx集团-智信部
电话:137****0806
邮箱:guofs@139.com
项目:os-w项目
UUID:A5BE85E0-3450-11E1-B698-8909460D0291