guodj преди 4 години
родител
ревизия
c86bb18505

+ 11 - 0
boot/boot.go

@@ -0,0 +1,11 @@
+package boot
+
+/*
+	自动化脚本
+	全局初始化事件
+*/
+func init() {
+
+	// nsq
+	NsqInit()
+}

+ 19 - 0
boot/nsq.go

@@ -0,0 +1,19 @@
+package boot
+
+import (
+	"dashoo.cn/micro_libary/nsq"
+	"github.com/gogf/gf/frame/g"
+	"lims_adapter/service/reservation"
+)
+
+// NsqInit  nsq初始化
+func NsqInit() {
+	// 生产者初始化
+	nsq.NSqProducer = nsq.NewNsqProducer()
+
+	// 预约消息订阅
+	autoProcessTopic := g.Cfg().GetString("nsq.nsqReservationAutoProcess")
+	if autoProcessTopic != "" {
+		go reservation.ReceiverReservation(autoProcessTopic)
+	}
+}

+ 6 - 2
common/date.go

@@ -44,14 +44,18 @@ func GetCNWeekday(time *gtime.Time) int {
 // GetNextTimeNode 获取当前时间的下一个预约节点
 func GetNextTimeNode(signOutTime *gtime.Time) *gtime.Time {
 	currentMinute := signOutTime.Minute()
+	// 当分钟为00或者30的时候直接返回
+	if currentMinute == 30 && signOutTime.Second() == 0 || currentMinute == 0 && signOutTime.Second() == 0 {
+		return signOutTime
+	}
 	if currentMinute >= 30 {
 		nextTimeStr := fmt.Sprintf("%v-%.2v-%.2v %.2v:%.2v:%.2v",
-			signOutTime.Year(), int(signOutTime.Month()), signOutTime.Day(),
+			signOutTime.Year(), signOutTime.Month(), signOutTime.Day(),
 			signOutTime.Hour()+1, 0, 0)
 		return gtime.NewFromStr(nextTimeStr)
 	}
 	nextTimeStr := fmt.Sprintf("%v-%.2v-%.2v %.2v:%.2v:%.2v",
-		signOutTime.Year(), int(signOutTime.Month()), signOutTime.Day(),
+		signOutTime.Year(), signOutTime.Month(), signOutTime.Day(),
 		signOutTime.Hour(), 30, 0)
 	return gtime.NewFromStr(nextTimeStr)
 }

+ 31 - 0
common/date_test.go

@@ -30,3 +30,34 @@ func TestGetCNEndOfWeek(t *testing.T) {
 		})
 	}
 }
+
+func TestGetNextTimeNode(t *testing.T) {
+	type args struct {
+		signOutTime *gtime.Time
+	}
+	t1 := gtime.NewFromStr("2021-09-16 11:44:56")
+	w1 := gtime.NewFromStr("2021-09-16 12:00:00")
+
+	t2 := gtime.NewFromStr("2021-09-16 00:00:00")
+	w2 := gtime.NewFromStr("2021-09-16 00:00:00")
+
+	t3 := gtime.NewFromStr("2021-09-16 23:42:00")
+	w3 := gtime.NewFromStr("2021-09-17 00:00:00")
+
+	tests := []struct {
+		name string
+		args args
+		want *gtime.Time
+	}{
+		{"普通时间测试", args{t1}, w1},
+		{"零点时间测试", args{t2}, w2},
+		{"24点时间测试", args{t3}, w3},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := GetNextTimeNode(tt.args.signOutTime); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("GetNextTimeNode() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}

+ 5 - 0
config/config.toml

@@ -14,6 +14,11 @@
     end_at = "24:00:00"
     weekday = "1,2,3,4,5,6,7"
 
+[nsq]
+    nsqLookupd = "192.168.0.252:4161"
+    nsqd = "192.168.0.252:4150"
+    nsqReservationAutoProcess = "reservation_auto_process"
+
 [service_registry]
     registry = "consul"  # consul 或 peer2peer
 #    server-addr = "192.168.0.63:18090"

+ 3 - 0
dao/internal/meeting_reservation.go

@@ -46,6 +46,7 @@ type meetingReservationColumns struct {
 	RealityUseDuration string // 实际使用时长
 	UserName           string // 预约人姓名
 	Status             string // 预约状态(1:预定   2:取消)
+	RealEndTimeNode    string // 实际结束时间节点(用于增加预约时的校验)
 }
 
 var (
@@ -75,6 +76,7 @@ var (
 			RealityUseDuration: "RealityUseDuration",
 			UserName:           "UserName",
 			Status:             "Status",
+			RealEndTimeNode:    "RealEndTimeNode",
 		},
 	}
 )
@@ -106,6 +108,7 @@ func NewMeetingReservationDao(tenant string) MeetingReservationDao {
 			RealityUseDuration: "RealityUseDuration",
 			UserName:           "UserName",
 			Status:             "Status",
+			RealEndTimeNode:    "RealEndTimeNode",
 		},
 	}
 	return dao

+ 4 - 0
go.mod

@@ -30,6 +30,7 @@ require (
 	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
 	github.com/gogf/mysql v1.6.1-0.20210603073548-16164ae25579 // indirect
 	github.com/gogo/protobuf v1.3.1 // indirect
+	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
 	github.com/golang/snappy v0.0.2 // indirect
 	github.com/gomodule/redigo v2.0.0+incompatible // indirect
@@ -60,6 +61,8 @@ require (
 	github.com/miekg/dns v1.1.27 // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/mitchellh/mapstructure v1.4.1 // indirect
+	github.com/mojocn/base64Captcha v1.3.1 // indirect
+	github.com/nsqio/go-nsq v1.0.8 // indirect
 	github.com/nxadm/tail v1.4.8 // indirect
 	github.com/olekukonko/tablewriter v0.0.5 // indirect
 	github.com/onsi/ginkgo v1.16.4 // indirect
@@ -86,6 +89,7 @@ require (
 	go.opentelemetry.io/otel/metric v0.19.0 // indirect
 	go.opentelemetry.io/otel/trace v0.19.0 // indirect
 	golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee // indirect
+	golang.org/x/image v0.0.0-20190501045829-6d32002ffd75 // indirect
 	golang.org/x/mod v0.4.2 // indirect
 	golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect

+ 24 - 0
handler/reservation.go

@@ -142,3 +142,27 @@ func (r *Reservation) ReserveInfo(ctx context.Context, req *model.ReserveReq, rs
 	rsp.Msg = msg
 	return nil
 }
+
+// Ending 提前结束
+func (r *Reservation) Ending(ctx context.Context, req *model.EndingReq, rsp *comm_def.CommonMsg) error {
+	tenant, err := micro_srv.GetTenant(ctx)
+	if err != nil {
+		return err
+	}
+	g.Log().Info("Received Reservation.Ending request @ " + tenant)
+	// 校验
+	if req.Date.String() == "" {
+		return NoParamsErr
+	}
+	if req.Id == 0 {
+		return NoParamsErr
+	}
+	err = reservation.NewSrv(tenant).Ending(ctx, *req)
+	_, err, code, msg := myerrors.CheckError(err)
+	if err != nil {
+		return err
+	}
+	rsp.Code = code
+	rsp.Msg = msg
+	return nil
+}

+ 1 - 0
main.go

@@ -7,6 +7,7 @@ import (
 	"dashoo.cn/micro_libary/micro_srv"
 	"github.com/gogf/gf/frame/g"
 	"github.com/smallnest/rpcx/protocol"
+	_ "lims_adapter/boot"
 )
 
 func main() {

+ 2 - 1
model/internal/meeting_reservation.go

@@ -10,7 +10,7 @@ import (
 
 // MeetingReservation is the golang structure for table meeting_reservation.
 type MeetingReservation struct {
-	Id                 uint        `orm:"Id"         json:"id"`                           // 主键ID
+	Id                 uint        `orm:"Id,primary"         json:"id"`                   // 主键ID
 	CreatedBy          string      `orm:"CreatedBy"          json:"created_by"`           // 创建人姓名
 	CreatedAt          *gtime.Time `orm:"CreatedAt"          json:"created_at"`           // 创建时间
 	UpdatedAt          *gtime.Time `orm:"UpdatedAt"          json:"updated_at"`           // 更新时间
@@ -30,4 +30,5 @@ type MeetingReservation struct {
 	RealityUseDuration float64     `orm:"RealityUseDuration" json:"reality_use_duration"` // 实际使用时长
 	UserName           string      `orm:"UserName"           json:"user_name"`            // 预约人姓名
 	Status             int         `orm:"Status"             json:"status"`               // 预约状态(1:预定   2:取消)
+	RealEndTimeNode    *gtime.Time `orm:"RealEndTimeNode"    json:"real_end_time_node"`   // 实际结束时间节点(用于增加预约时的校验)
 }

+ 13 - 6
model/meeting_reservation.go

@@ -68,12 +68,13 @@ type OverviewReq struct {
 }
 
 type ShortList struct {
-	Id        int         `orm:"Id,primary"         json:"id"`        // 主键ID
-	Title     string      `orm:"Title"              json:"title"`     // 标题/会议名称
-	StartTime *gtime.Time `orm:"StartTime"          json:"startTime"` // 开始时间
-	EndTime   *gtime.Time `orm:"EndTime"            json:"endTime"`   // 结束时间
-	UserId    int         `orm:"UserId"             json:"user_id"`   // 预约人
-	EntityId  int         `orm:"EntityId"           json:"entity_id"` // 关联ID
+	Id          int         `orm:"Id,primary"         json:"id"`            // 主键ID
+	Title       string      `orm:"Title"              json:"title"`         // 标题/会议名称
+	StartTime   *gtime.Time `orm:"StartTime"          json:"startTime"`     // 开始时间
+	EndTime     *gtime.Time `orm:"EndTime"            json:"endTime"`       // 结束时间
+	UserId      int         `orm:"UserId"             json:"user_id"`       // 预约人
+	EntityId    int         `orm:"EntityId"           json:"entity_id"`     // 关联ID
+	SignOutTime *gtime.Time `orm:"SignOutTime"        json:"sign_out_time"` // 签退时间
 }
 
 // ReserveReq 预约请求
@@ -92,3 +93,9 @@ type ReservationList struct {
 	Uname     string `json:"uname"`      // 用户名称
 	Week      int    `json:"week"`       // 本周时间
 }
+
+// EndingReq 预约请求
+type EndingReq struct {
+	Id   int         `json:"id"`   // Id
+	Date *gtime.Time `json:"date"` // 当前时间
+}

+ 68 - 0
service/reservation/autopress.go

@@ -0,0 +1,68 @@
+package reservation
+
+import (
+	utilNsq "dashoo.cn/micro_libary/nsq"
+	"encoding/json"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/glog"
+	"github.com/nsqio/go-nsq"
+	"time"
+)
+
+// AutoSignOut 自动写入签退时间
+type AutoSignOut struct {
+	Id     int    `json:"id"`
+	Tenant string `json:"tenant"`
+}
+
+func (a *AutoSignOut) HandleMessage(message *nsq.Message) error {
+	if message.Body != nil {
+		glog.Info("收到会议室预约的延时消息,内容为:", string(message.Body))
+		err := json.Unmarshal(message.Body, a)
+		if err != nil {
+			glog.Error("nsq body json unmarshal err:", err.Error())
+		}
+		if a == nil {
+			glog.Error("json unmarshal is null")
+		}
+		if a.Tenant == "" || a.Id == 0 {
+			glog.Error("参数错误, 本次操作终止执行")
+		}
+		err = NewSrv(a.Tenant).AutoEnding(a.Id)
+		if err != nil {
+			glog.Error("auto press error:", err)
+		}
+		return nil
+	}
+	return nil
+}
+
+// ReceiverReservation 延迟消息接收
+func ReceiverReservation(topic string) {
+	// 从配置文件中获取主题
+	nsqChannel := utilNsq.NsqChannlNameForSrv()
+	consumer, err := utilNsq.NewConsumerForHandler(topic, "reservation"+nsqChannel, new(AutoSignOut))
+	if err != nil {
+		glog.Error("create nsq consumer err:", err.Error())
+	}
+	<-consumer.StopChan
+}
+
+// AutoProcess 发送延迟消息
+func AutoProcess(id int, tenant string, delayTime time.Duration) {
+	topic := g.Cfg().GetString("nsq.nsqReservationAutoProcess")
+	expire := AutoSignOut{
+		Tenant: tenant,
+		Id:     id,
+	}
+	msg, err := json.Marshal(expire)
+	if err != nil {
+		glog.Error("appoint auto process json marshal err:", err.Error())
+	}
+	// 发送延时消息
+	err = utilNsq.NSqProducer.DeferredPublish(topic, delayTime, msg)
+	if err != nil {
+		glog.Error("发送预约类的延时消息失败,err:", err.Error())
+	}
+	glog.Info("发送延时消息, 内容为:", string(msg), "延时时间为:", delayTime)
+}

+ 79 - 3
service/reservation/reservation.go

@@ -3,6 +3,7 @@ package reservation
 import (
 	"context"
 	"dashoo.cn/common_definition/admin/user_def"
+	"dashoo.cn/micro_libary/micro_srv"
 	"dashoo.cn/micro_libary/request"
 	"errors"
 	"fmt"
@@ -95,10 +96,14 @@ func (s Service) Add(userInfo request.UserInfo, req model.ReservationReq) error
 	entity.UserName = userInfo.RealName
 	entity.UserId = int(userInfo.Id)
 	entity.DepartmentId = gconv.Int(userInfo.DeptId)
-	_, err = s.Dao.M.Insert(entity)
+	entity.RealEndTimeNode = req.EndTime
+	res, err := s.Dao.M.Insert(entity)
 	if err != nil {
 		return err
 	}
+	id, _ := res.LastInsertId()
+	// 延迟消息
+	go AutoProcess(int(id), s.Tenant, req.EndTime.Sub(gtime.Now()))
 	return nil
 }
 
@@ -161,6 +166,66 @@ func (s Service) ReserveInfo(ctx context.Context, req model.ReserveReq) (g.Map,
 	}, nil
 }
 
+// Ending 结束会议
+func (s Service) Ending(ctx context.Context, req model.EndingReq) error {
+	// 先查询预约记录
+	entity, err := s.Dao.WherePri(req.Id).FindOne()
+	if err != nil {
+		return err
+	}
+	if entity == nil {
+		return errors.New("未找到当前预约信息")
+	}
+	if entity.SignOutTime.String() != "" {
+		return errors.New("当前预约已提前结束")
+	}
+	// 校验日期是不是同一天(24:00除外) 校验日期是不是在预约开始时间之后
+	if req.Date.Sub(entity.StartTime) <= 0 {
+		return errors.New("结束时间不能早于会议预约时间")
+	}
+	if req.Date.Format("Y-m-d") != entity.StartTime.Format("Y-m-d") &&
+		req.Date.Hour() != 0 && req.Date.Minute() != 0 {
+		return errors.New("结束时间必须为当天时间")
+	}
+	if (req.Date.Day()-1) != entity.StartTime.Day() && req.Date.Hour() == 0 && req.Date.Minute() == 0 {
+		return errors.New("当前结束时间错误,请重试")
+	}
+	if req.Date.Sub(entity.EndTime) > 0 {
+		return errors.New("预约时间正常结束的会议无需再次结束")
+	}
+	userInfo, err := micro_srv.GetUserInfo(ctx)
+	if err != nil {
+		return err
+	}
+	if userInfo.Id != int32(entity.UserId) {
+		return errors.New("当前预约只能由本人结束")
+	}
+	// 更新实际结束时间
+	updatedMap := service.SetUpdatedMap(&userInfo)
+	updatedMap[s.Dao.Columns.SignOutTime] = req.Date
+	updatedMap[s.Dao.Columns.RealEndTimeNode] = common.GetNextTimeNode(req.Date)
+	_, err = s.Dao.M.WherePri(req.Id).Update(updatedMap)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (s Service) AutoEnding(id int) error {
+	entity, err := s.Dao.WherePri(id).FindOne()
+	if err != nil {
+		return err
+	}
+	if entity.SignOutTime.String() != "" {
+		return nil
+	}
+	_, err = s.Dao.WherePri(id).Update(g.Map{s.Dao.Columns.SignOutTime: entity.EndTime})
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
 // Check 预约校验
 func (s Service) check(req model.ReservationReq) error {
 	// 校验开始结束时间不能跨天
@@ -187,10 +252,12 @@ func (s Service) check(req model.ReservationReq) error {
 		Where(s.Dao.Columns.StartTime+">= ? AND "+s.Dao.Columns.StartTime+"< ?", req.StartTime, req.EndTime)
 	entityModel = entityModel.
 		WhereOr(s.Dao.Columns.EntityId+"= ? AND "+s.Dao.Columns.Status+" = 1 AND "+
-			s.Dao.Columns.EndTime+"> ? AND "+s.Dao.Columns.EndTime+"<= ?", req.EntityId, req.StartTime, req.EndTime)
+			s.Dao.Columns.RealEndTimeNode+"> ? AND "+s.Dao.Columns.RealEndTimeNode+"<= ?",
+			req.EntityId, req.StartTime, req.EndTime)
 	entityModel = entityModel.
 		WhereOr(s.Dao.Columns.EntityId+"= ? AND "+s.Dao.Columns.Status+" = 1 AND "+
-			s.Dao.Columns.EndTime+">= ? AND "+s.Dao.Columns.StartTime+"<= ?", req.EntityId, req.EndTime, req.StartTime)
+			s.Dao.Columns.RealEndTimeNode+">= ? AND "+s.Dao.Columns.StartTime+"<= ?",
+			req.EntityId, req.EndTime, req.StartTime)
 	exist, err := entityModel.Count()
 	if err != nil {
 		return err
@@ -234,6 +301,12 @@ func (s Service) shortList(date *gtime.Time) ([]model.ShortList, error) {
 		return nil, nil
 	}
 	res.Structs(&list)
+	for k, _ := range list {
+		// 当签退时间小于预约结束时间,使用签退时间
+		if list[k].SignOutTime.String() != "" && list[k].EndTime.Sub(list[k].SignOutTime) > 0 {
+			list[k].EndTime = common.GetNextTimeNode(list[k].SignOutTime)
+		}
+	}
 	return list, nil
 }
 
@@ -302,6 +375,9 @@ func (s Service) getCurrentWeekReservation(ctx context.Context, req model.Reserv
 	resultList := make([]model.ReservationList, 0)
 	for _, v := range list {
 		timeLong := v.EndTime.Sub(v.StartTime).Hours()
+		if v.SignOutTime.String() != "" && v.EndTime.Sub(v.SignOutTime) > 0 {
+			timeLong = common.GetNextTimeNode(v.SignOutTime).Sub(v.StartTime).Hours()
+		}
 		grid := timeLong * 2
 		for i := 0; i < int(grid); i++ {
 			startAt := v.StartTime.Add(time.Duration(i) * 30 * time.Minute)

+ 6 - 5
service/srv/user_test.go

@@ -3,6 +3,7 @@ package srv
 import (
 	"context"
 	"dashoo.cn/common_definition/admin/user_def"
+	"log"
 	"testing"
 )
 
@@ -19,11 +20,10 @@ func TestGetUserList(t *testing.T) {
 		want    []user_def.UserInfoList
 		wantErr bool
 	}{
-		{"微服务调用-用户列表测试", args{
-			ctx:    context.TODO(),
-			ids:    []int64{1},
-			tenant: "CU7zm9WhZm",
-		}, want01, false},
+		{"微服务调用-用户列表测试",
+			args{context.TODO(), []int64{1}, "CU7zm9WhZm"}, want01, false},
+		{"微服务调用-用户列表测试02",
+			args{context.TODO(), []int64{1}, "EmGVD5szuT"}, want01, false},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -35,6 +35,7 @@ func TestGetUserList(t *testing.T) {
 			//if !reflect.DeepEqual(got, tt.want) {
 			//	t.Errorf("GetUserList() got = %v, want %v", got, tt.want)
 			//}
+			log.Println(got)
 			if len(got) != len(tt.want) {
 				t.Errorf("GetUserList() got = %v, want %v", got, tt.want)
 			}

+ 16 - 0
test/reservation.http

@@ -78,4 +78,20 @@ Authorization: Bearer LygMayNczyP8dlrHOmq+/zjw1/jzxfzELII5bN0syADTzKW0pfMr7sLc+Q
   "date":"2021-09-15",
   "entity_id":15
 }
+###
+
+### 提前结束
+POST http://192.168.0.63:9981/dashoo.lims.adapter-0.1-guodj
+Content-Type: application/json
+X-RPCX-SerializeType: 1
+X-RPCX-ServicePath: Reservation
+X-RPCX-ServiceMethod: Ending
+Tenant:EmGVD5szuT
+SrvEnv: dev
+Authorization: Bearer HGnbMS/1YhzZ7PWdrPKHgSU767s1tA72w3hgvPXvqup0olyNYGXcDnQG4jPcFJGy81T18/DNppIU9yNX4pWnmw==
+
+{
+  "date":"2021-09-16 15:10:26",
+  "id":38
+}
 ###