考勤新增未打卡, 微信公众号通知

This commit is contained in:
andy 2025-08-30 09:37:55 +08:00
parent 3d5a53ef71
commit 4e24321256
19 changed files with 558 additions and 75 deletions

View File

@ -229,6 +229,18 @@
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
<version>2.8.9</version> <version>2.8.9</version>
</dependency> </dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>5.8.35</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -24,8 +24,10 @@ import com.evo.system.domain.SysStaffDetail;
import com.evo.system.mapper.SysDeptMapper; import com.evo.system.mapper.SysDeptMapper;
import com.evo.system.mapper.SysStaffDetailMapper; import com.evo.system.mapper.SysStaffDetailMapper;
import com.evo.system.mapper.SysStaffMapper; import com.evo.system.mapper.SysStaffMapper;
import com.evo.wechat.service.SendClientService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -63,6 +65,9 @@ public class KQDeviceExchangeProcessor implements PunchTheClockStrategyExchangeP
@Resource @Resource
private SysStaffDetailMapper sysStaffDetailMapper; private SysStaffDetailMapper sysStaffDetailMapper;
@Resource
private SendClientService sendClientService;
@Override @Override
public boolean accept(String sn) { public boolean accept(String sn) {
//餐饮打卡机不下发按钮 //餐饮打卡机不下发按钮
@ -222,81 +227,29 @@ public class KQDeviceExchangeProcessor implements PunchTheClockStrategyExchangeP
RzAttendance beforeAttendance = rzAttendanceMapper.queryNowDayAttendanceByStatisticalIdAndDate(Long.valueOf(userId),DateUtils.addDays(date, -1)); RzAttendance beforeAttendance = rzAttendanceMapper.queryNowDayAttendanceByStatisticalIdAndDate(Long.valueOf(userId),DateUtils.addDays(date, -1));
if(beforeAttendance.getWorkStartTime() != null && beforeAttendance.getWorkEndTime() == null){ if(beforeAttendance.getWorkStartTime() != null && beforeAttendance.getWorkEndTime() == null){
notBeforeEndTimeMessage = " - 昨天未打下班卡"; notBeforeEndTimeMessage = " - 昨天未打下班卡";
sendAbnormalAttendance(userId, beforeAttendance.getWorkStartTime());
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
log.error("查询前一天的打卡情况报错了"); log.error("查询前一天的打卡情况报错了");
} }
return (rzAttendanceMapper.updateRzAttendance(attendance) > 0) ? initMessage(0, "打卡成功"+notBeforeEndTimeMessage) : initMessage(1, "打卡失败"+notBeforeEndTimeMessage); return (rzAttendanceMapper.updateRzAttendance(attendance) > 0) ? initMessage(0, "打卡成功"+notBeforeEndTimeMessage) : initMessage(1, "打卡失败"+notBeforeEndTimeMessage);
} }
// /*** 上班卡的下班卡 @Async
// * @param date public void sendAbnormalAttendance(Long userId, Date date){
// * @param rules try {
// * @return SysStaff sysStaff = sysStaffMapper.selectById(userId);
// */ if(sysStaff != null && StringUtils.isNotEmpty(sysStaff.getOpenid())){
// public String workOffDutyCard(Date date, String rules, RzAttendance attendance){ SysDept sysDept = sysDeptMapper.selectById(sysStaff.getDeptId());
// sendClientService.sendAbnormalAttendance(sysDept != null ? sysDept.getDeptName() : "未知",sysStaff.getName(), "昨天下班未打卡", date, sysStaff.getOpenid());
// //计算工时 }
// Long hours = (date.getTime() - attendance.getWorkStartTime().getTime())/1000/60/60; } catch (Exception e) {
// log.error("推送公众号打卡异常失败, 原因为: {}", e.getMessage(), e);
// EqButton eqButton = eqButtonMapper.selectOne(new LambdaQueryWrapper<EqButton>().eq(EqButton::getName, rules)); }
// }
// Integer maxWorkHour = ParamUtils.getTsWorkHour(attendance.getName());
// if(maxWorkHour == null){
// maxWorkHour = eqButton.getWorkHour();
// }
//
// //判断打卡时间, 添加夜班次数 打卡时间在晚上7点以后
// if(eqButton.getWorkHour() ==12 && hours >= 12 && attendance.getWorkStartTime().getHours() > 12){
// attendance.setNightNumber(1);
// }else if(eqButton.getWorkHour() == 8 && hours >= 8 && attendance.getWorkStartTime().getHours() > 18){
// attendance.setMiddleShiftNumber(1);
// }
// //获取工作时长
// BigDecimal res = new BigDecimal(hours);
// if(hours.compareTo(Long.valueOf(maxWorkHour)) >= 0){
// res = new BigDecimal(maxWorkHour);
// }
//// else if(hours.compareTo(Long.valueOf(eqButton.getWorkHour()/2)) > 0){
//// res = new BigDecimal(eqButton.getWorkHour()/2);
//// }
// //检查是否异常
// if(checkYc("下班", rules, sdf.format(date), sdfd.format(date))){
// attendance.setYcxFlag("1");
// }
//
// attendance.setWorkEndTime(date);
// attendance.setWorkSum(res);
// return (rzAttendanceMapper.updateRzAttendance(attendance) > 0) ? initMessage(0, "打卡成功") : initMessage(1,"打卡失败");
// }
// /*** 校验是否上班卡打卡异常
// * @param rules
// * @param dateTime
// * @param date
// * @return
// */
// public Boolean checkYc(String type, String rules, String dateTime, String date){
// String script = ParamUtils.getYcRulesByButtonName(type, rules);
// if(StringUtils.isEmpty(script)){
// return false;
// }
// ScriptEngineManager manager = new ScriptEngineManager();
// ScriptEngine engine = manager.getEngineByName("javascript");
// try {
// Object result = engine.eval(script.replaceAll("dkDateTime",dateTime).replaceAll("dkDate", date));
// System.out.println("检查结果"+result);
// return new Boolean(String.valueOf(result));
// } catch (Exception e) {
// logger.error("打卡交易是否异常出现错误{}", e.getMessage());
// }
// //默认为没有异常
// return false;
// }
/*** 撤销卡 /*** 撤销卡
* @param date * @param date
* @return * @return

View File

@ -12,6 +12,7 @@ import com.evo.personnelMatters.mapper.RzOverTimeMapper;
import com.evo.restaurant.service.IRzRestaurantStatisticsService; import com.evo.restaurant.service.IRzRestaurantStatisticsService;
import com.evo.system.domain.SysStaff; import com.evo.system.domain.SysStaff;
import com.evo.system.service.ISysStaffService; import com.evo.system.service.ISysStaffService;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -81,6 +82,36 @@ public class TestController {
@GetMapping("/message")
public String message(Model model, String code) {
System.out.println(code);
// String res = AccessTokenUtil.accessToken2("wx268e32962db19f5f", "84a6065165ec82862c5e03a010a6dc6c", code);
// System.out.println(res);
// JSONObject obj = JSONObject.parseObject(res);
// String openid = obj.getString("openid");
SysStaff user = new SysStaff();
user.setOpenid(code);
model.addAttribute("user", user);
//model.addAttribute("openid", openid);
// ErrorData templateData = new ErrorData();
//
// templateData.setThing1((JSONObject)JSON.toJSON(Collections.asMap("value", "研发部")));
// templateData.setThing2((JSONObject)JSON.toJSON(Collections.asMap("value", "张三")));//动态跟换车牌号
// templateData.setConst4((JSONObject)JSON.toJSON(Collections.asMap("value", "昨天下班未打卡")));//动态跟换车牌号
// templateData.setTime6((JSONObject)JSON.toJSON(Collections.asMap("value", DateUtil.format(new Date(), "yyyy年MM月dd日"))));//动态跟换车牌号
//
// MessageTemplateSendData sendData = new MessageTemplateSendData();
// sendData.setTouser(openid);
// sendData.setTemplate_id("z9sy-38K-iC5MAWHbxcxwg1c-9oNTFWeCOoy6B6zdKY");
// sendData.setData((JSONObject)JSON.toJSON(templateData));
// TemplateMessageUtil.templateMessageSend(GZHAccessTokenService.gzhAccessToken(), sendData);
return "user_info.html";
}
/** /**
* 清洗加班 * 清洗加班
*/ */

View File

@ -7,7 +7,9 @@ import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data; import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
@ -24,6 +26,7 @@ public class SysDept extends BaseEntity
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 部门ID */ /** 部门ID */
@TableId(value = "dept_id", type = IdType.AUTO)
private Long deptId; private Long deptId;
/** 父部门ID */ /** 父部门ID */

View File

@ -36,6 +36,28 @@ public class RedisCache
redisTemplate.opsForValue().set(key, value); redisTemplate.opsForValue().set(key, value);
} }
/**
* 缓存基本的对象IntegerString实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Long timeout)
{
try {
if (timeout > 0) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
} else {
setCacheObject(key, value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/** /**
* 缓存基本的对象IntegerString实体类等 * 缓存基本的对象IntegerString实体类等
* *

View File

@ -116,7 +116,8 @@ public class SecurityConfig
// 静态资源可匿名访问 // 静态资源可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
.antMatchers("/websocket/**").permitAll() .antMatchers("/wechat/**").permitAll()
.antMatchers("/websocket/**").permitAll()
.antMatchers("/test/**").permitAll() .antMatchers("/test/**").permitAll()
.antMatchers("/api/v1/verify_user").permitAll() .antMatchers("/api/v1/verify_user").permitAll()
.antMatchers("/api/v1/record/face").permitAll() .antMatchers("/api/v1/record/face").permitAll()

View File

@ -2,10 +2,7 @@ package com.evo.restaurant.service.impl;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.evo.common.constant.Constants; import com.evo.common.constant.Constants;
import com.evo.common.core.domain.AjaxResult;
import com.evo.common.utils.DateUtils;
import com.evo.common.utils.ParamUtils; import com.evo.common.utils.ParamUtils;
import com.evo.common.utils.SecurityUtils;
import com.evo.common.utils.StringUtils; import com.evo.common.utils.StringUtils;
import com.evo.restaurant.domain.RzRestaurantDetail; import com.evo.restaurant.domain.RzRestaurantDetail;
import com.evo.restaurant.domain.RzRestaurantImages; import com.evo.restaurant.domain.RzRestaurantImages;
@ -19,6 +16,7 @@ import com.evo.restaurant.service.IRzRestaurantDetailService;
import com.evo.system.domain.SysStaff; import com.evo.system.domain.SysStaff;
import com.evo.system.mapper.SysStaffMapper; import com.evo.system.mapper.SysStaffMapper;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
@ -63,6 +61,7 @@ public class RzRestaurantDetailServiceImpl implements IRzRestaurantDetailService
@Override @Override
public String insertRzRestaurantDetail(String json) public String insertRzRestaurantDetail(String json)
{ {
System.out.println(json.toString());
//需要返回的对象 //需要返回的对象
RestaurantVo cfiv = new RestaurantVo(); RestaurantVo cfiv = new RestaurantVo();
RestaurantData cfd = new RestaurantData(); RestaurantData cfd = new RestaurantData();
@ -76,7 +75,8 @@ public class RzRestaurantDetailServiceImpl implements IRzRestaurantDetailService
//if(hour!=7&&hour!=8&&hour!=11&&hour!=12&&hour!=13&&hour!=18&&hour!=19&&hour!=20){ //if(hour!=7&&hour!=8&&hour!=11&&hour!=12&&hour!=13&&hour!=18&&hour!=19&&hour!=20){
if(!ParamUtils.getCanteenOpenHour().contains(hour)){ if(!ParamUtils.getCanteenOpenHour().contains(hour)){
cfiv.setResult(2); cfiv.setResult(2);
cfiv.setMsg("{\"Result\":1,\"Msg\":\"不在打卡时间内\"}"); cfiv.setMsg("不在打卡时间内");
System.out.println(JSONObject.toJSONString(cfiv));
return JSONObject.toJSONString(cfiv); return JSONObject.toJSONString(cfiv);
} }
@ -90,7 +90,7 @@ public class RzRestaurantDetailServiceImpl implements IRzRestaurantDetailService
RzRestaurantImages img_obj = rzRestaurantImagesMapper.selectRzRestaurantImagesById(Long.valueOf(userId)); RzRestaurantImages img_obj = rzRestaurantImagesMapper.selectRzRestaurantImagesById(Long.valueOf(userId));
if(StringUtils.isNull(sysStaff) && StringUtils.isNull(img_obj)){ if(StringUtils.isNull(sysStaff) && StringUtils.isNull(img_obj)){
cfiv.setResult(2); cfiv.setResult(2);
cfiv.setMsg("{\"Result\":1,\"Msg\":\"验证失败,未设置考勤权限\"}"); cfiv.setMsg("当前人员信息不存在");
return JSONObject.toJSONString(cfiv); return JSONObject.toJSONString(cfiv);
} }
@ -100,6 +100,9 @@ public class RzRestaurantDetailServiceImpl implements IRzRestaurantDetailService
if(StringUtils.isNotNull(restaurantDetail) && (new Date().getTime() - restaurantDetail.getTime().getTime()) < 1000*60*60*3){ if(StringUtils.isNotNull(restaurantDetail) && (new Date().getTime() - restaurantDetail.getTime().getTime()) < 1000*60*60*3){
//如果不是第一次打卡则弹窗提示 //如果不是第一次打卡则弹窗提示
cfiv.setResult(1); cfiv.setResult(1);
//返回打卡次数
cfd.setClock_in_count(1);
cfiv.setContent(cfd);
cfiv.setMsg("重复打卡!!"); cfiv.setMsg("重复打卡!!");
return JSONObject.toJSONString(cfiv); return JSONObject.toJSONString(cfiv);
} }
@ -134,17 +137,17 @@ public class RzRestaurantDetailServiceImpl implements IRzRestaurantDetailService
int i = rzRestaurantDetailMapper.insertRzRestaurantDetail(cyFaceInfo); int i = rzRestaurantDetailMapper.insertRzRestaurantDetail(cyFaceInfo);
if(i<1){ if(i<1){
cfiv.setResult(2); cfiv.setResult(2);
cfiv.setMsg("{\"Result\":1,\"Msg\":\"打卡失败\"}"); cfiv.setMsg("打卡失败");
return JSONObject.toJSONString(cfiv); return JSONObject.toJSONString(cfiv);
} }
//反写统计次数 //反写统计次数
i = rzRestaurantStatisticsMapper.updateRzRestaurantStatistics(restaurantStatistics); i = rzRestaurantStatisticsMapper.updateRzRestaurantStatistics(restaurantStatistics);
if(i<1){ if(i<1){
cfiv.setResult(2); cfiv.setResult(2);
cfiv.setMsg("{\"Result\":1,\"Msg\":\"打卡失败\"}"); cfiv.setMsg("打卡失败");
return JSONObject.toJSONString(cfiv); return JSONObject.toJSONString(cfiv);
} }
cfiv.setMsg("{\"Result\":1,\"Msg\":\"打卡成功\"}"); cfiv.setMsg("打卡成功");
cfd.setClock_in_count(1); cfd.setClock_in_count(1);
cfiv.setContent(cfd); cfiv.setContent(cfd);
cfiv.setResult(0); cfiv.setResult(0);

View File

@ -198,6 +198,11 @@ public class SysStaff extends BaseEntity
* 打卡位置 * 打卡位置
*/ */
private String timeClock; private String timeClock;
/***
* 公众号的openId
*/
private String openid;
/*** /***
* 享有补贴 * 享有补贴
*/ */

View File

@ -0,0 +1,48 @@
package com.evo.wechat;
import cn.hutool.http.HttpUtil;
import java.util.HashMap;
import java.util.Map;
/**
* 调用凭据
*/
public class AccessTokenUtil {
private static String accessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token";
/**
* 获取接口调用凭据
* GET https://api.weixin.qq.com/cgi-bin/token
* @param appid
* @param secret
* @return
*/
public static String accessToken(String appid, String secret) {
Map<String, Object> m = new HashMap<String, Object>();
m.put("appid", appid);
m.put("secret", secret);
m.put("grant_type", "client_credential");
String res = HttpUtil.get(accessTokenUrl, m);
return res;
}
private static String accessTokenUrl2 = "https://api.weixin.qq.com/sns/oauth2/access_token";
/**
* 获取接口调用凭据
* GET https://api.weixin.qq.com/cgi-bin/token
* @return
*/
public static String accessToken2(String appid, String secret, String code) {
Map<String, Object> m = new HashMap<String, Object>();
m.put("appid",appid);
m.put("secret", secret);
m.put("code", code);
m.put("grant_type", "authorization_code");
String res = HttpUtil.get(accessTokenUrl2, m);
return res;
}
}

View File

@ -0,0 +1,28 @@
package com.evo.wechat;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson2.JSONObject;
import com.evo.wechat.dto.MessageTemplate;
public class TemplateMessageUtil {
private static String templateSendUrl = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=";
/**
* 发送模板消息 POST
* @param accessToken
* @param templateData
* @return
*/
public static String templateMessageSend(String accessToken, MessageTemplate templateData) {
String body = JSONObject.toJSONString(templateData);
String res = HttpRequest.post(templateSendUrl + accessToken)
.header("Content-Type", "application/json")
.body(body)
.execute()
.body();
return res;
}
}

View File

@ -0,0 +1,67 @@
package com.evo.wechat;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.evo.system.domain.SysStaff;
import com.evo.system.service.ISysStaffService;
import com.evo.wechat.config.GZHProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.Resource;
/**
*
*
* @ClassName:WeChatController
* @date: 2025年08月29日 16:39
* @author: andy.shi
* @remark: 开发人员联系方式 1042025947@qq.com/微信同步
*/
@Slf4j
@Controller
@RequestMapping("/wechat")
public class WeChatController {
@Resource
private GZHProperties gzhProperties;
@Resource
private ISysStaffService sysStaffService;
@GetMapping("/callback")
public String message(Model model, String code) {
log.info("获取到的code{}", code);
String res = AccessTokenUtil.accessToken2(gzhProperties.getAppid(), gzhProperties.getAppSecret(), code);
log.info("获取到的openId相关信息{}", code);
JSONObject obj = JSONObject.parseObject(res);
SysStaff user = new SysStaff();
user.setOpenid(obj.getString("openid"));
model.addAttribute("user", user);
return "user_info.html";
}
@PostMapping("/submit_user")
public String submitUser(Model model, @ModelAttribute("user") SysStaff user) {
log.info("获取到的code{}", user.toString());
SysStaff sysStaff = sysStaffService.getOne(new LambdaQueryWrapper<SysStaff>().eq(SysStaff::getIdCard, user.getIdCard()), false);
if(ObjectUtils.isEmpty(sysStaff)){
sysStaff = sysStaffService.getOne(new LambdaQueryWrapper<SysStaff>().eq(SysStaff::getPhone, user.getPhone()), false);
}
if(ObjectUtils.isEmpty(sysStaff)){
model.addAttribute("message", "处理失败, 请检查你的个人信息录入是否正确");
return "success.html";
}
sysStaff.setOpenid(user.getOpenid());
sysStaffService.updateById(sysStaff);
model.addAttribute("message", "处理成功");
return "success.html";
}
}

View File

@ -0,0 +1,20 @@
package com.evo.wechat.config;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "yt.gzh", ignoreUnknownFields = true)
@Data
@Schema(description = "公众号信息")
public class GZHProperties {
private String appid;
private String appSecret;
//打卡异常通知
private String abnormalAttendanceTemplateId;
}

View File

@ -0,0 +1,20 @@
package com.evo.wechat.dto;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
/**
* 考勤异常消息模版
*/
@Data
public class AbnormalAttendanceTemplate {
//部门
private JSONObject thing1;
//姓名
private JSONObject thing2;
//异常类型 昨天上班未打卡 or 昨天下班未打卡
private JSONObject const4;
//考勤异常日期
private JSONObject time6;
}

View File

@ -0,0 +1,29 @@
package com.evo.wechat.dto;
import com.alibaba.fastjson2.JSONObject;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
@Data
public class MessageTemplate implements Serializable {
private static final long serialVersionUID = -8911097073333862L;
private String touser;
private String template_id;
@Schema(description = "模板跳转链接")
private String url;
@Schema(description = "防重入id。对于同一个openid + client_msg_id, 只发送一条消息,10分钟有效")
private String client_msg_id;
@Schema(description = "模板数据")
private JSONObject data;
}

View File

@ -0,0 +1,38 @@
package com.evo.wechat.service;
import com.alibaba.fastjson2.JSONObject;
import com.evo.common.core.redis.RedisCache;
import com.evo.wechat.AccessTokenUtil;
import com.evo.wechat.config.GZHProperties;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
@Service
public class GZHAccessTokenService {
@Resource
GZHProperties gzhProperties;
@Resource
private RedisCache redisUtil;
private String accessTokenPrefix = "hd:wechat:accessToken:gzh:";
public String gzhAccessToken() {
if (redisUtil.hasKey(accessTokenPrefix + gzhProperties.getAppid())) {
return redisUtil.getCacheObject(accessTokenPrefix + gzhProperties.getAppid()).toString();
}
String res = AccessTokenUtil.accessToken(gzhProperties.getAppid(),gzhProperties.getAppSecret());
JSONObject jo = JSONObject.parseObject(res);
// {"errcode":40013,"errmsg":"invalid appid rid: 6708ba65-0b425b74-4620599c"}
if (StringUtils.hasText(jo.getString("errcode"))) {
throw new RuntimeException(res);
}
redisUtil.setCacheObject(accessTokenPrefix + gzhProperties.getAppid(), jo.getString("access_token"), 7200L);
return jo.getString("access_token");
}
}

View File

@ -0,0 +1,47 @@
package com.evo.wechat.service;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.evo.common.utils.Collections;
import com.evo.wechat.TemplateMessageUtil;
import com.evo.wechat.config.GZHProperties;
import com.evo.wechat.dto.AbnormalAttendanceTemplate;
import com.evo.wechat.dto.MessageTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Date;
/**
*
*
* @ClassName:SendClientService
* @date: 2025年08月29日 16:53
* @author: andy.shi
* @remark: 开发人员联系方式 1042025947@qq.com/微信同步
*/
@Service
public class SendClientService {
@Resource
GZHAccessTokenService gzhAccessTokenService;
@Resource
GZHProperties gzhProperties;
public void sendAbnormalAttendance(String deptName, String userName, String info, Date date, String openId){
AbnormalAttendanceTemplate templateData = new AbnormalAttendanceTemplate();
templateData.setThing1((JSONObject) JSON.toJSON(Collections.asMap("value", deptName)));
templateData.setThing2((JSONObject)JSON.toJSON(Collections.asMap("value",userName)));//动态跟换车牌号
templateData.setConst4((JSONObject)JSON.toJSON(Collections.asMap("value", info)));//动态跟换车牌号
templateData.setTime6((JSONObject)JSON.toJSON(Collections.asMap("value", DateUtil.format(date, "yyyy年MM月dd日"))));//动态跟换车牌号
MessageTemplate sendData = new MessageTemplate();
sendData.setTouser(openId);
sendData.setTemplate_id(gzhProperties.getAbnormalAttendanceTemplateId());
sendData.setData((JSONObject)JSON.toJSON(templateData));
TemplateMessageUtil.templateMessageSend(gzhAccessTokenService.gzhAccessToken(), sendData);
}
}

View File

@ -52,7 +52,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectSysStaffVo"> <sql id="selectSysStaffVo">
select user_id,company_name, dept_id, code, name, id_card,is_leader, sex, age, phone, address, level, major, school, bank_number,social_subsidy, bank, employment_date, experience, worker_term, regular_date, quit_date, contract_start, contract_end, contract_type, social_type, seniority, is_overtime_pay, zs_flag, secrecy, injury, insurance, introducer, clock_in, status, wages_ratio_date, remarks, del_flag, create_by, create_time, update_by, update_time, image_url,time_clock,subsidys, job_code from sys_staff select user_id,company_name, dept_id, code, name, id_card,is_leader, sex, age, phone, address, level, major, school, bank_number,social_subsidy, bank, employment_date, experience, worker_term, regular_date, quit_date, contract_start, contract_end, contract_type, social_type, seniority, is_overtime_pay, zs_flag, secrecy, injury, insurance, introducer, clock_in, status, wages_ratio_date, remarks, del_flag, create_by, create_time, update_by, update_time, image_url,time_clock,subsidys, job_code, openid from sys_staff
</sql> </sql>
<select id="selectSysStaffList" parameterType="com.evo.system.domain.SysStaff" resultMap="SysStaffResult"> <select id="selectSysStaffList" parameterType="com.evo.system.domain.SysStaff" resultMap="SysStaffResult">
@ -134,6 +134,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="timeClock != null">time_clock,</if> <if test="timeClock != null">time_clock,</if>
<if test="subsidys != null">subsidys,</if> <if test="subsidys != null">subsidys,</if>
<if test="jobCode != null">job_code,</if> <if test="jobCode != null">job_code,</if>
<if test="openid != null">openid,</if>
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="companyName != null">#{companyName},</if> <if test="companyName != null">#{companyName},</if>
@ -181,6 +183,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="timeClock != null">#{timeClock},</if> <if test="timeClock != null">#{timeClock},</if>
<if test="subsidys != null">#{subsidys},</if> <if test="subsidys != null">#{subsidys},</if>
<if test="jobCode != null">#{jobCode},</if> <if test="jobCode != null">#{jobCode},</if>
<if test="openid != null">#{openid},</if>
</trim> </trim>
</insert> </insert>
@ -232,6 +235,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="timeClock != null">time_clock = #{timeClock},</if> <if test="timeClock != null">time_clock = #{timeClock},</if>
<if test="subsidys != null">subsidys = #{subsidys},</if> <if test="subsidys != null">subsidys = #{subsidys},</if>
<if test="jobCode != null">job_Code=#{jobCode},</if> <if test="jobCode != null">job_Code=#{jobCode},</if>
<if test="openid != null">openid=#{openid},</if>
</trim> </trim>
where user_id = #{userId} where user_id = #{userId}
</update> </update>

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Hello</title>
</head>
<body>
<h1 th:text="${message}"></h1>
</body>
</html>

View File

@ -0,0 +1,143 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户信息提交</title>
<!-- 引入Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- 引入Font Awesome -->
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- 自定义Tailwind配置 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3b82f6',
secondary: '#64748b',
accent: '#10b981',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.form-input-focus {
@apply focus:ring-2 focus:ring-primary/50 focus:border-primary focus:outline-none;
}
.form-label {
@apply block text-sm font-medium text-gray-700 mb-1;
}
.form-input {
@apply w-full px-4 py-2 border border-gray-300 rounded-lg transition duration-200 form-input-focus;
}
.error-message {
@apply text-red-500 text-sm mt-1;
}
}
</style>
</head>
<body class="bg-gray-50 min-h-screen flex items-center justify-center p-4">
<div class="w-full max-w-md bg-white rounded-xl shadow-lg overflow-hidden">
<div class="p-6 sm:p-8">
<div class="text-center mb-6">
<h2 class="text-2xl font-bold text-gray-900">用户信息信息完善</h2>
<p class="mt-2 text-sm text-gray-500">请填写以下信息并提交</p>
</div>
<!-- 表单使用Thymeleaf绑定提交到/submit-user路径 -->
<form th:action="@{/wechat/submit_user}" th:object="${user}" method="post" class="space-y-5">
<!-- 用户姓名字段 -->
<div>
<label for="name" class="form-label">
<i class="fa fa-user-circle mr-1"></i>用户姓名
</label>
<input type="text" id="name" th:field="*{name}"
class="form-input"
placeholder="请输入您的姓名"
required>
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="error-message"></p>
</div>
<!-- 身份证号字段 -->
<div>
<label for="idCard" class="form-label">
<i class="fa fa-id-card mr-1"></i>身份证号
</label>
<input type="text" id="idCard" th:field="*{idCard}"
class="form-input"
placeholder="请输入18位身份证号"
maxlength="18"
required>
<p th:if="${#fields.hasErrors('idCard')}" th:errors="*{idCard}" class="error-message"></p>
</div>
<!-- 手机号字段 -->
<div>
<label for="phone" class="form-label">
<i class="fa fa-phone mr-1"></i>手机号
</label>
<input type="tel" id="phone" th:field="*{phone}"
class="form-input"
placeholder="请输入11位手机号"
maxlength="11"
required>
<p th:if="${#fields.hasErrors('phone')}" th:errors="*{phone}" class="error-message"></p>
</div>
<!-- 隐藏文本输入框 -->
<!-- <input type="tel" id="openid" th:field="*{openid}"-->
<!-- th:value="${openid}"-->
<!-- class="form-input"-->
<!-- readonly>-->
<input type="hidden" id="openid" th:field="*{openid}" th:value="${openid}">
<!-- 提交按钮 -->
<div>
<button type="submit" class="w-full bg-primary hover:bg-primary/90 text-white font-medium py-2.5 px-4 rounded-lg transition duration-200 flex items-center justify-center">
<i class="fa fa-paper-plane mr-2"></i>提交信息
</button>
</div>
</form>
<!-- 提交成功消息(如果有) -->
<div th:if="${successMessage}" class="mt-5 p-3 bg-green-50 border border-green-200 rounded-lg text-green-700 text-sm">
<i class="fa fa-check-circle mr-1"></i>
<span th:text="${successMessage}"></span>
</div>
</div>
</div>
<!-- 简单的表单验证脚本 -->
<script>
// 表单提交前的基本验证
document.querySelector('form').addEventListener('submit', function(e) {
const idCard = document.getElementById('idCard').value;
const phone = document.getElementById('phone').value;
let isValid = true;
// 身份证号简单验证18位
if (idCard.length !== 18) {
alert('请输入18位身份证号');
isValid = false;
}
// 手机号简单验证11位数字
if (!/^\d{11}$/.test(phone)) {
alert('请输入有效的11位手机号');
isValid = false;
}
if (!isValid) {
e.preventDefault(); // 阻止表单提交
}
});
</script>
</body>
</html>