This commit is contained in:
2025-07-07 20:11:59 +08:00
parent ab0fdbc447
commit 06e3aa2eb3
2009 changed files with 193082 additions and 0 deletions

View File

@ -0,0 +1,308 @@
// @Author cory 2025/3/5 10:27:00
package attendanceMachine
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/tiger1103/gfast/v3/api/v1/common/coryCommon"
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
wxDao "github.com/tiger1103/gfast/v3/internal/app/wxApplet/dao"
wxBusAttendance "github.com/tiger1103/gfast/v3/internal/app/wxApplet/logic/busAttendance"
wxModel "github.com/tiger1103/gfast/v3/internal/app/wxApplet/model"
wxDo "github.com/tiger1103/gfast/v3/internal/app/wxApplet/model/do"
"github.com/tiger1103/gfast/v3/library/liberr"
tool "github.com/tiger1103/gfast/v3/utility/coryUtils"
"golang.org/x/net/context"
"io/ioutil"
"math"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
func (w AttendanceMachineApi) TaskCreate(ctx context.Context, req *EquipmentTimeClockReq) (res *EquipmentTimeClockRes, err error) {
res = new(EquipmentTimeClockRes)
err = AttendanceMachine(ctx, req)
res.Result = "0"
res.Msg = ""
r := ghttp.RequestFromCtx(ctx)
r.Response.ClearBuffer()
r.Response.WriteJson(res)
return
}
func AttendanceMachine(ctx context.Context, req *EquipmentTimeClockReq) (err error) {
logs := req.Logs[0]
f1 := logs.Location.Longitude
f2 := logs.Location.Latitude
res, err := wxBusAttendance.ThisInverseGeocodingFunc(f1 + "," + f2)
liberr.ErrIsNil(ctx, err)
// 上次打卡距离此次打卡不超过3分支那么提示“你刚刚已打上/下班卡了哦!”
currentTime := time.Now()
date := tool.New().GetFormattedDate(currentTime)
var bair *wxModel.BusAttendanceInfoRes
err = wxDao.BusAttendance.Ctx(ctx).
Where("openid", logs.UserID).
Where("printing_date", date).
Fields("clock_on,commuter").OrderDesc("clock_on").Limit().Scan(&bair)
liberr.ErrIsNil(ctx, err, "打卡失败!")
if err == nil && bair != nil {
//判断t1和t2是否相差180秒如果小于180秒就直接return
flag, err := IsWithinThreeMinutes(currentTime, bair.ClockOn)
if err != nil {
return err
}
if flag {
txt := ""
if bair.Commuter == "1" {
txt = "您已上班打卡成功!杜绝重复打卡!"
} else {
txt = "您已下班打卡成功!杜绝重复打卡!"
}
err = errors.New(txt)
return err
}
}
lng, err := strconv.ParseFloat(f1, 64)
if err != nil {
err = errors.New("精准度转换错误!")
return
}
lat, err := strconv.ParseFloat(f2, 64)
if err != nil {
err = errors.New("纬度转换错误!")
return
}
lng, lat = coryCommon.GCJ02toWGS84(lng, lat)
// 0-0、判断是否开启打卡功能 (禁止打卡和离职都不允许打卡)
var userInfo *wxModel.BusConstructionUserInfoRes
err = dao.BusConstructionUser.Ctx(ctx).Where("openid", logs.UserID).Scan(&userInfo)
if userInfo.TeamId == 0 {
err = errors.New("当前用户暂无班组,无法打卡!")
return
}
// 查看当前用户的打卡时间是否请假,如果请假就提示不用打卡
count, err := wxDao.BusAskforleave.Ctx(ctx).
Where(wxDao.BusAskforleave.Columns().ProjectId, userInfo.ProjectId).
Where(wxDao.BusAskforleave.Columns().Openid, userInfo.Openid).
Where("(" +
"DATE_FORMAT(" + date + ",'%Y-%m-%d') BETWEEN DATE_FORMAT(start_time,'%Y-%m-%d') AND DATE_FORMAT(end_time,'%Y-%m-%d')" +
" or " +
"DATE_FORMAT(" + date + ",'%Y-%m-%d') BETWEEN DATE_FORMAT(start_time,'%Y-%m-%d') AND DATE_FORMAT(end_time,'%Y-%m-%d')" +
")").Count()
liberr.ErrIsNil(ctx, err)
if count > 0 {
liberr.ErrIsNil(ctx, errors.New("您已请假,无需打卡!"))
return
}
// 部分班组可以直接跳过在范围内打卡,获取当前用户班组,看看是否是可以在任何地点打卡
value, errvalue := wxDao.SysProjectTeam.Ctx(ctx).Where("id", userInfo.TeamId).Fields("is_clock_in").Value()
if errvalue != nil {
err = errors.New("获取班组打卡状态失败!")
return err
}
if value.String() == "2" {
goto breakHere
}
// 计算是否在打卡范围中
if true {
// 查询当前用户是否有加入班组,有的话看看是什么项目,再根据项目获取到方正,最后根据方正数据得出打卡范围
projectId, _ := wxDao.SysProjectTeamMember.Ctx(ctx).As("a").
LeftJoin("sys_project_team as b on a.team_id = b.id").
Fields("a.project_id").
Where("a.openid", logs.UserID).Value()
if projectId != nil {
var qf []*wxBusAttendance.ProjectPunchRangeRes
err = g.DB().Model("sys_project_punch_range").Where("project_id", projectId).Fields("punch_range").Scan(&qf)
if err != nil {
err = errors.New("系统错误,请联系管理员!")
return err
}
if len(qf) == 0 {
err = errors.New("未设置打卡范围!请联系管理员设置!")
return err
}
countI := len(qf)
countII := 0
for _, dakaStr := range qf {
countII = countII + 1
var dataInfo coryCommon.DetailedMap
err = json.Unmarshal([]byte(fmt.Sprint(dakaStr.PunchRange)), &dataInfo.Positions)
flag := coryCommon.RectangularFrameRange(dataInfo, lng, lat)
if flag {
goto breakHere
} else {
// 没有一次范围匹配上,那就直接返回
if countII == countI {
err = errors.New("不在范围内,打卡无效!")
return err
}
}
}
} else {
err = errors.New("未指定打卡范围,请联系管理员!")
return err
}
}
breakHere:
if userInfo.Status == "1" {
err = errors.New("已离职,请联系管理员!")
return
}
if userInfo.Clock == "2" {
err = errors.New("已禁止打卡,请联系管理员!")
return
}
if len(strings.Trim(userInfo.LeaveDate.String(), "")) != 0 {
err = errors.New("已退场,请联系管理员!")
return
}
clockStatus := ""
// 1、获取当日时间
// 2、查询今日打卡记录
gm := wxDao.BusAttendance.Ctx(ctx).
Where(wxDao.BusAttendance.Columns().Openid, logs.UserID).
Where(wxDao.BusAttendance.Columns().PrintingDate, date)
// 3、如果有判断是否打了上班卡如果没有就打上班卡
count1, _ := gm.Where("commuter", "1").Count()
if count1 == 0 {
// 打上班卡
clockStatus = "1"
}
// 4、如果有判断是否打了下班卡如果没有就打下班卡
count2, _ := gm.Where("commuter", "2").Count()
if clockStatus == "" && count2 == 0 {
// 打下班卡
clockStatus = "2"
}
// 5、上下班卡都打了那么就提示今日打卡完成
if count1 != 0 && count2 != 0 {
// 已完成当日打卡
err = errors.New("当日打卡已完成!")
return err
}
// 提交数据
name := userInfo.NickName
if userInfo.UserName != "" {
name = userInfo.UserName
}
// 将base64人脸换成图片存储到本地
imagePath, err := DecodeBase64Image(logs.Photo)
if err != nil {
return err
}
rpath := coryCommon.ResourcePublicToFunc(imagePath, 1)
attendance := wxDo.BusAttendance{
PacePhoto: rpath,
UserName: name,
ProjectId: userInfo.ProjectId,
Openid: userInfo.Openid,
Commuter: clockStatus,
Lng: lng,
Lat: lat,
PrintingDate: date,
}
// 判断此次打卡状态 默认就为缺勤
dateTime := tool.New().GetFormattedDateTime(time.Now())
if userInfo.ProjectId > 0 {
value, err := dao.SysProject.Ctx(ctx).WherePri(userInfo.ProjectId).Fields("punch_range as punchRange").Value()
if err != nil {
err = errors.New("获取项目打卡范围失败!")
}
split := strings.Split(value.String(), ",")
strType := tool.New().TimeWithin(dateTime, split[0], split[1], clockStatus)
attendance.IsPinch = strType
attendance.PunchRange = value.String() // 记录项目的打卡时间,保留字段,后期有大用
}
// 获取此次打卡的日薪
vl, err := dao.BusConstructionUser.Ctx(ctx).As("a").
LeftJoin("bus_type_of_wage as f on a.type_of_work = f.type_of_work").
Fields("if (a.salary>0, a.salary,f.standard) as salary").
Where("a.openid", logs.UserID).Value()
liberr.ErrIsNil(ctx, err, "获取薪资失败")
// 记录打卡时间上下班打卡时间统一用clock_on
attendance.ClockOn = dateTime
attendance.DailyWage = vl
attendance.Lng = lng
attendance.Lat = lat
attendance.Location = res.Regeocode.FormattedAddress
_, err = wxDao.BusAttendance.Ctx(ctx).Insert(attendance)
liberr.ErrIsNil(ctx, err, "添加失败")
return
}
// DecodeBase64Image 函数用于解码 Base64 编码的图片数据并保存为图片
func DecodeBase64Image(encodedStr string) (string, error) {
// 进行 URL 解码
decodedURLStr, err := url.QueryUnescape(encodedStr)
if err != nil {
return "", fmt.Errorf("URL 解码失败: %w", err)
}
// 提取真正的 Base64 数据
parts := strings.SplitN(decodedURLStr, ",", 2)
if len(parts) != 2 {
return "", fmt.Errorf("无效的 Base64 编码图片数据格式")
}
base64Data := parts[1]
// 进行 Base64 解码
decodedData, err := base64.StdEncoding.DecodeString(base64Data)
if err != nil {
return "", fmt.Errorf("Base64 解码失败: %w", err)
}
ht := coryCommon.Helmet
str := coryCommon.Ynr(ht)
fn := coryCommon.FileName("face")
dir, err := os.Getwd()
str = dir + "/" + str + fn + ".jpg"
str = filepath.ToSlash(str)
// 保存解码后的图片到文件
err = ioutil.WriteFile(str, decodedData, 0644)
if err != nil {
fmt.Println("保存图片文件出错:", err)
return "", fmt.Errorf("保存解码后的图片失败: %w", err)
}
return str, nil
}
// 判断两个时间是否相差超过 3 分钟
func isMinutesDifferenceGreaterThanThree(startTime, endTime time.Time) bool {
// 计算时间差(单位:秒)
diff := endTime.Sub(startTime).Seconds()
// 判断是否超过 180 秒3 分钟)
return diff > 180
}
// 判断两个时间的秒数差是否 <= 180 秒
func IsWithinThreeMinutes(currentTime time.Time, strTime string) (bool, error) {
// 解析字符串时间,使用本地时区
loc, _ := time.LoadLocation("Local") // 获取本地时区
parsedTime, err := time.ParseInLocation("2006-01-02 15:04:05", strTime, loc)
if err != nil {
fmt.Println("时间解析错误:", err)
return false, err
}
// 计算时间差(秒)
diff := math.Abs(currentTime.Sub(parsedTime).Seconds())
// 判断是否相差 180 秒
return diff <= 180, nil
}