初始化siyue

This commit is contained in:
wyh 2022-03-03 17:08:44 +08:00
commit c977643f79
12 changed files with 589 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
cok-ble
.vscode
*.sum
*.yml
*.yaml

28
Makefile Normal file
View File

@ -0,0 +1,28 @@
.PHONY: arm
arm:
go mod tidy
GOOS=linux GOARCH=arm GOARM=7 go build -o april .
.PHONY: windows
windows:
go mod tidy
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o april.exe .
.PHONY: macos
macos:
go mod tidy
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build .
.PHONY: release
release: arm
ssh pi@192.168.0.27 "sudo systemctl stop april.service"
scp ./april pi@192.168.0.27:~/
ssh pi@192.168.0.27 "sudo systemctl start april.service"
.PHONY: nodejs
nodejs:
npm run build
.PHONY: front
front:
scp -r public/dist/* pi@192.168.0.21:~/demo/

4
README.md Normal file
View File

@ -0,0 +1,4 @@
### BLE 考勤采集
- 获取蓝牙广播数据
- 上报信息至服务端

1
cok-ble.http Normal file
View File

@ -0,0 +1 @@
@url=localhost:8080/cok-ble

8
gin/config.go Normal file
View File

@ -0,0 +1,8 @@
package gin
//GIN 配置
type Config struct {
RootPath string
Addr string
Port int
}

33
gin/gin.go Normal file
View File

@ -0,0 +1,33 @@
package gin
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
func Service(conf *Config) {
if conf == nil {
conf = &Config{
RootPath: "/",
Addr: "0.0.0.0",
Port: 80,
}
}
router := gin.New()
routerSetup(router, &conf.RootPath)
s := &http.Server{
Addr: fmt.Sprintf("%s:%d", conf.Addr, conf.Port),
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
logrus.Printf("start service on %s", fmt.Sprintf("%s:%d", conf.Addr, conf.Port))
logrus.Fatal(s.ListenAndServe())
}

18
gin/router.go Normal file
View File

@ -0,0 +1,18 @@
package gin
import (
"fmt"
"github.com/gin-gonic/gin"
)
//路由配置
func routerSetup(router *gin.Engine, rootpath *string) {
router.Use(gin.Recovery())
router.GET(`/health/check`)
r := router.Group(fmt.Sprintf("/%s", *rootpath))
{
r.POST(`/register`)
}
}

18
go.mod Normal file
View File

@ -0,0 +1,18 @@
module myschools.me/wyh/ble-april
go 1.16
require (
github.com/gin-gonic/gin v1.7.7
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
github.com/lestrrat-go/strftime v1.0.5 // indirect
github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
github.com/shirou/gopsutil v2.21.11+incompatible
github.com/sirupsen/logrus v1.8.1
github.com/spf13/viper v1.10.1
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/vmihailenco/msgpack v3.3.3+incompatible
github.com/yusufpapurcu/wmi v1.2.2 // indirect
)

52
logger.go Normal file
View File

@ -0,0 +1,52 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"time"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"github.com/rifflock/lfshook"
"github.com/sirupsen/logrus"
)
func init() {
//日志初始化
// logrus.SetOutput(os.Stdout)
logrus.SetOutput(ioutil.Discard)
logrus.SetFormatter(&logrus.TextFormatter{})
logrus.SetLevel(logrus.DebugLevel)
logrus.AddHook(newLfsHook(7))
}
func newLfsHook(maxRemainCnt uint) logrus.Hook {
//检查与创建日志文件夹
_, err := os.Stat("logs")
if os.IsNotExist(err) {
os.Mkdir("logs", 0755)
}
logName := fmt.Sprintf(`logs/%s`, APPNAME)
writer, err := rotatelogs.New(
logName+"%Y%m%d.log",
rotatelogs.WithLinkName(logName),
rotatelogs.WithRotationTime(24*time.Hour),
rotatelogs.WithRotationCount(maxRemainCnt),
)
if err != nil {
panic("config local file system for logger error: " + err.Error())
}
lfsHook := lfshook.NewHook(lfshook.WriterMap{
logrus.DebugLevel: writer,
logrus.InfoLevel: writer,
logrus.WarnLevel: writer,
logrus.ErrorLevel: writer,
logrus.FatalLevel: writer,
logrus.PanicLevel: writer,
}, &logrus.TextFormatter{})
return lfsHook
}

40
main.go Normal file
View File

@ -0,0 +1,40 @@
package main
import (
"flag"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"myschools.me/wyh/ble-april/gin"
"myschools.me/wyh/ble-april/service"
)
const (
APPNAME = "ble-april"
)
func main() {
cf := flag.String("config", "april.yml", "file of config")
flag.Parse()
viper.SetConfigFile(*cf)
if err := viper.ReadInConfig(); err != nil {
logrus.WithFields(logrus.Fields{
"func": "main",
}).Fatalf("%s", err.Error())
}
//ble服务
service.BleService()
go service.BleMessagePush()
go service.BleCacheClear()
go service.Bletokeninit()
go service.BleHeartBeat()
go gin.Service(&gin.Config{
RootPath: APPNAME,
Addr: viper.GetString("api.host"),
Port: viper.GetInt("api.port"),
})
select {}
}

337
service/ble-service.go Normal file
View File

@ -0,0 +1,337 @@
package service
import (
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"time"
"github.com/paypal/gatt"
"github.com/paypal/gatt/examples/option"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/mem"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"github.com/vmihailenco/msgpack"
)
var m map[string]*IBeancon
var chn chan *IBeancon
var token string
var expiretime time.Time
func init() {
m = make(map[string]*IBeancon, 500)
chn = make(chan *IBeancon, 500)
}
type Bletoken struct {
AppKey string
AppSecret string
}
type BleResponse struct {
Code int
Msg string
Data struct {
Token string
Expire int64
}
}
func Bletokeninit() {
for {
if time.Now().Before(expiretime) {
time.Sleep(5 * time.Minute)
continue
}
bb, err := json.Marshal(&Bletoken{
AppKey: viper.GetString("api.appkey"),
AppSecret: viper.GetString("api.appsecret"),
})
if err != nil {
logrus.WithFields(logrus.Fields{
"func": "Bletokeninit",
}).Warnf("%s", err.Error())
continue
}
reader := strings.NewReader(string(bb))
//生成要访问的url
url := fmt.Sprintf("%s/oauth", viper.GetString("srv.host"))
resp, err := http.Post(url, "application/json;charset=UTF-8", reader)
if err != nil {
logrus.WithFields(logrus.Fields{
"func": "BleMessagePush",
}).Warnf("http.post: %s", err.Error())
continue
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
logrus.Println("ioutil.ReadAll:", err.Error())
continue
}
logrus.Println("body", string(body))
// 解析返回值,正确时添加入内存
retbody := &BleResponse{}
err = json.Unmarshal(body, retbody)
if err != nil {
logrus.Println("err: ", err)
}
token = retbody.Data.Token
expire := retbody.Data.Expire
expiretime = time.Now().Add(time.Duration(time.Duration(expire) * time.Second))
}
}
//蓝牙接收服务
func BleService() {
go func() {
dev, err := gatt.NewDevice(option.DefaultClientOptions...)
if err != nil {
log.Fatalf("Failed to open device, err:%s\n", err)
}
dev.Handle(
gatt.PeripheralDiscovered(onPerhipheralDiscovered),
)
dev.Init(onStateChanged)
}()
}
func onStateChanged(device gatt.Device, s gatt.State) {
switch s {
case gatt.StatePoweredOn:
device.Scan([]gatt.UUID{}, true)
return
default:
device.StopScanning()
}
}
func onPerhipheralDiscovered(p gatt.Peripheral, a *gatt.Advertisement, rssi int) {
//小于设定阈值不处理
if rssi < viper.GetInt("option.rssi") {
return
}
// 实例化蓝牙数据
b, err := NewiBeacon(a.ManufacturerData, p.ID())
if err != nil {
return
}
if beacon := m[b.DeviceID]; beacon != nil {
beacon.Updated = b.HappenTime.Unix()
m[b.DeviceID] = beacon
return
}
// 更新持续时长和更新时间
chn <- b
}
// 取出数据进行处理
func BleMessagePush() {
for {
if b, ok := <-chn; ok {
// // 上传服务器
bb, err := json.Marshal(b)
if err != nil {
continue
}
reader := strings.NewReader(string(bb))
// 发送请求
url := fmt.Sprintf("%s/receive", viper.GetString("srv.host"))
client := &http.Client{}
//生成要访问的url
//提交请求
reqest, err := http.NewRequest("POST", url, reader)
//增加header选项
reqest.Header.Set("token", token)
reqest.Header.Set("Content-Type", "application/json;charset=UTF-8")
if err != nil {
logrus.WithFields(logrus.Fields{
"func": "BleMessagePush",
}).Warnf("%s", err.Error())
continue
}
//处理返回结果
resp, err := client.Do(reqest)
if err != nil {
logrus.WithFields(logrus.Fields{
"func": "BleMessagePush",
}).Warnf("%s", err.Error())
continue
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
logrus.Println("ioutil.ReadAll:", err.Error())
continue
}
logrus.Println("body", string(body))
// 解析返回值,正确时添加入内存
i := make(map[string]interface{}, 0)
err = json.Unmarshal(body, &i)
if err != nil {
logrus.Println("err: ", err.Error())
continue
}
if i["msg"] == "ok" {
m[b.DeviceID] = b
}
}
}
}
type IBeancon struct {
DeviceID string
HappenTime time.Time
Updated int64 `json:"-"`
}
type BleHeart struct {
Cpu float64
Mem float64
Disk float64
}
func BleHeartBeat() {
for {
time.Sleep(1 * time.Minute)
// 判断60内有无通讯
if len(m) != 0 {
continue
}
percent, _ := cpu.Percent(time.Second, false)
memInfo, _ := mem.VirtualMemory()
parts, _ := disk.Partitions(true)
diskInfo, _ := disk.Usage(parts[0].Mountpoint)
heart := &BleHeart{
Cpu: percent[0],
Mem: memInfo.UsedPercent,
Disk: diskInfo.UsedPercent,
}
ht, err := json.Marshal(heart)
if err != nil {
logrus.Println("json.Marshal: ", err.Error())
continue
}
reader := strings.NewReader(string(ht))
// 发送请求
url := fmt.Sprintf("%s/heart", viper.GetString("srv.host"))
client := &http.Client{}
//生成要访问的url
//提交请求
reqest, err := http.NewRequest("POST", url, reader)
//增加header选项
reqest.Header.Set("token", token)
reqest.Header.Set("Content-Type", "application/json;charset=UTF-8")
if err != nil {
logrus.WithFields(logrus.Fields{
"func": "BleMessagePush",
}).Warnf("%s", err.Error())
continue
}
//处理返回结果
resp, err := client.Do(reqest)
if err != nil {
logrus.WithFields(logrus.Fields{
"func": "BleMessagePush",
}).Warnf("%s", err.Error())
continue
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
logrus.Println("ioutil.ReadAll:", err.Error())
continue
}
logrus.Println("heart", string(body))
// 解析返回值,正确时添加入内存
i := make(map[string]interface{}, 0)
err = json.Unmarshal(body, &i)
if err != nil {
logrus.Println("err: ", err.Error())
continue
}
if i["msg"] != "ok" {
logrus.WithFields(logrus.Fields{
"func": "BleHeartBeat",
}).Warnf("%s", errors.New("Push Heart Fail"))
continue
}
// 没有通讯发送请求cpu内存磁盘占用率
}
}
type BeaconAprilBag struct {
VFirmware string `json:"v-firmware"`
MID uint `json:"mid"`
Time int64 `json:"time"`
IP string `json:"ip"`
Mac string `json:"mac"`
Devices []byte `json:"devices"`
}
type BeaconApril struct {
AdvType string
Mac string
Rssi int
AdvertisementData string
}
func NewiBeacon(data []byte, mac string) (*IBeancon, error) {
out1 := &BeaconAprilBag{}
if err := msgpack.Unmarshal(data, &out1); err != nil {
logrus.WithFields(logrus.Fields{
"func": "NewiBeacon",
}).Warnf("%s", err.Error())
return nil, err
}
// 解析out1.device
// bytes.Split()
if len(data) < 25 || binary.BigEndian.Uint32(data) != 0x4c000215 {
return nil, errors.New("not an iBeacon")
}
beacon := new(IBeancon)
beacon.DeviceID = mac
beacon.HappenTime = time.Now()
beacon.Updated = time.Now().Unix()
return beacon, nil
}
// 清除缓存
func BleCacheClear() {
t := viper.GetInt64("option.interval")
if t < 30 {
t = 30
}
for {
now := time.Now().Unix()
for k, v := range m {
if now-v.Updated > t {
delete(m, k)
}
}
time.Sleep(time.Duration(t/5) * time.Second)
}
}

45
service/http-service.go Normal file
View File

@ -0,0 +1,45 @@
package service
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/spf13/viper"
)
func httpPost(uri string, body interface{}) (*[]byte, error) {
bb, err := json.Marshal(body)
if err != nil {
return nil, err
}
reader := strings.NewReader(string(bb))
url := fmt.Sprintf("%s%s", viper.GetString("srv.host"), uri)
resp, err := http.Post(url, "application/json;charset=UTF-8", reader)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respbody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return &respbody, nil
}
func httpGet(uri string) (*[]byte, error) {
url := fmt.Sprintf("%s%s", viper.GetString("srv.host"), uri)
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respbody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return &respbody, nil
}