ruoyi-admin/src/main/resources/application-test.yml
@@ -198,6 +198,8 @@ conf: appId: wxe91f1af7638aa5dd secretId: a787e1a462715604e0c9528b6d8960d1 templateResultId: C4YA0kRIhoJYwKbhrNhxi8wcRLP4R92QME-lc6eygAo templateDateId: fWv6EnWlUNhR5Gu-6b6rv0xbk-yr7pB_kKH_Xnt7ts4 #OSS及短信配置 code: config: ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/TUserController.java
@@ -5,11 +5,13 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.basic.PageInfo; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.BaseModel; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.redis.service.RedisService; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.framework.web.service.TokenService; @@ -25,12 +27,22 @@ import com.ruoyi.system.query.KnowledgeListQuery; import com.ruoyi.system.query.TaskListQuery; import com.ruoyi.system.service.*; import com.ruoyi.system.utils.wx.body.resp.Code2SessionRespBody; import com.ruoyi.system.utils.wx.body.resq.Code2SessionResqBody; import com.ruoyi.system.utils.wx.model.WeixinProperties; import com.ruoyi.system.utils.wx.pojo.AppletUserDecodeData; import com.ruoyi.system.utils.wx.pojo.AppletUserEncrypteData; import com.ruoyi.system.utils.wx.tools.WxAppletTools; import com.ruoyi.system.utils.wx.tools.WxUtils; import com.ruoyi.system.vo.system.*; import com.sun.jna.platform.win32.LMAccess; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import javax.validation.Valid; @@ -52,6 +64,7 @@ * @author xiaochen * @since 2025-05-28 */ @Slf4j @Api(tags = "个人中心") @RestController @RequestMapping("/t-user") @@ -96,6 +109,31 @@ private TProblemEscalationService problemEscalationService; @Resource private TNoticeService noticeService; @Autowired private RestTemplate wxRestTemplate; @Autowired private WeixinProperties weixinProperties; @Resource private RedisService redisService; @ApiOperation(value = "通过code获得openid,获取用户信息",tags = {"微信小程序登录"}) @GetMapping("/openIdByJsCode") public R<String> openIdByJsCode(@RequestParam String code) { Long userId = tokenService.getLoginUser().getUserId(); SysUser sysUser = sysUserService.selectUserById(userId); if(Objects.isNull(sysUser)){ return R.fail("未查询到当前登录用户信息"); } if(StringUtils.hasLength(sysUser.getOpenId())){ return R.ok(); } log.info("<<<<<<<<换取openid开始<<<<<<<<:{}", code); WxAppletTools appletTools = new WxAppletTools(wxRestTemplate, weixinProperties, redisService); Code2SessionRespBody body = appletTools.getOpenIdByJscode2session(new Code2SessionResqBody().build(code)); String openid = body.getOpenid(); sysUser.setOpenId(openid); sysUserService.updateUser(sysUser); return R.ok(); } @ApiOperation(value = "消息通知列表") @PostMapping(value = "/noticeList") ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
@@ -163,4 +163,9 @@ * 用户类型 */ public static final String USER_TYPE = "用户类型"; /** * 模板消息发送地址 */ public static final String TEMPLATE_URL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=ACCESS_TOKEN"; } ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
@@ -152,6 +152,11 @@ @ApiModelProperty(value = "营业部id") @TableField("business_dept_id") private String businessDeptId; @ApiModelProperty(value = "openId") @TableField("openId") private String openId; public String getRoleName() { return roleName; ruoyi-common/src/main/java/com/ruoyi/common/utils/WxAppletTools.java
File was deleted ruoyi-system/src/main/java/com/ruoyi/system/utils/JsonUtils.java
New file @@ -0,0 +1,110 @@ package com.ruoyi.system.utils; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.system.utils.wx.config.JacksonConfig; import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; /** * Json转换工具类 * 参考:https://blog.csdn.net/weixin_38413579/article/details/82562634 * @author madman */ @Slf4j public final class JsonUtils { private static final ObjectMapper OM = new ObjectMapper(); private static final JavaTimeModule timeModule = new JavaTimeModule(); /** * 转换LocalDateTime */ static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> { @Override public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { jsonGenerator.writeString(localDateTime.format(DateTimeFormatter.ofPattern(JacksonConfig.dateTimeFormat))); } } /** * 转换LocalDate */ static class LocalDateSerializer extends JsonSerializer<LocalDate> { @Override public void serialize(LocalDate localDate, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { jsonGenerator.writeString(localDate.format(DateTimeFormatter.ofPattern(JacksonConfig.dateFormat))); } } /** * 设置 ObjectMapper * * @return */ private static ObjectMapper getObjectMapper() { // 序列化 timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer()); timeModule.addSerializer(LocalDate.class, new LocalDateSerializer()); // 反序列化 timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(JacksonConfig.dateTimeFormat))); timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(JacksonConfig.dateFormat))); // 允许对象忽略json中不存在的属性 OM.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); OM.registerModule(timeModule); return OM; } /** * 将对象序列化 */ public static <T> String toJsonString(T obj) { try { ObjectMapper om = getObjectMapper(); return om.writeValueAsString(obj); } catch (JsonProcessingException e) { log.error("转json字符串失败:{}", obj); return null; } } /** * 反序列化对象字符串 */ public static <T> T parseObject(String json, Class<T> clazz) { try { ObjectMapper om = getObjectMapper(); return om.readValue(json, clazz); } catch (JsonProcessingException e) { throw new ServiceException("反序列化对象字符串失败"); } } /** * 反序列化字符串成为对象 */ public static <T> T parseObject(String json, TypeReference<T> valueTypeRef) { try { ObjectMapper om = getObjectMapper(); return om.readValue(json, valueTypeRef); } catch (JsonProcessingException e) { throw new ServiceException("反序列化字符串成为对象失败"); } } } ruoyi-system/src/main/java/com/ruoyi/system/utils/TemplateMessageSendUtil.java
New file @@ -0,0 +1,82 @@ package com.ruoyi.system.utils; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.core.utils.HttpUtils; import com.ruoyi.common.redis.service.RedisService; import com.ruoyi.system.utils.wx.model.WeixinProperties; import com.ruoyi.system.utils.wx.template.Template; import com.ruoyi.system.utils.wx.template.wxTemplateTaskReturn.WxTemplateTaskResultRequest; import com.ruoyi.system.utils.wx.template.wxTemplateTaskReturn.WxTemplateTaskResultRequestData; import com.ruoyi.system.utils.wx.tools.WxAppletTools; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.io.Serializable; /** * 消息模板发送 */ @Slf4j @Component public class TemplateMessageSendUtil implements Serializable { private final static String ACCESSTOKEN_CACHE_KEY = "accessToken:"; @Autowired private RedisCache redisCache; @Autowired private RestTemplate wxRestTemplate; @Autowired private WeixinProperties weixinProperties; @Resource private RedisService redisService; /** * 任务数据提交结果通知 * @param openId 用户openId * @param taskName 任务名称 * @param auditResult 审核结果 * @param auditPerson 审核人 */ public void wxTemplateTaskResultRequest(String openId,String taskName,String auditResult,String auditPerson) { WxAppletTools appletTools = new WxAppletTools(wxRestTemplate, weixinProperties, redisService); // 1,获取access_token String accessToken = redisCache.getCacheObject(ACCESSTOKEN_CACHE_KEY); if (!StringUtils.hasLength(accessToken)) { accessToken = appletTools.getAccessToken(); } try { log.info("任务数据提交结果通知:------------------------"); // 创建请求实体 WxTemplateTaskResultRequest wxTemplateTaskResultRequest = new WxTemplateTaskResultRequest(); wxTemplateTaskResultRequest.setTouser(openId); wxTemplateTaskResultRequest.setTemplate_id(weixinProperties.getTaskResultTemplateId()); // 任务名称 Template thing1 = new Template(); thing1.setValue(taskName); // 审核结果 Template phrase4 = new Template(); phrase4.setValue(auditResult); // 审核人 Template thing3 = new Template(); thing3.setValue(auditPerson); // 发送模板消息参数实体封装 WxTemplateTaskResultRequestData wxTemplateTaskResultRequestData = new WxTemplateTaskResultRequestData(); wxTemplateTaskResultRequestData.setThing1(thing1); wxTemplateTaskResultRequestData.setPhrase4(phrase4); wxTemplateTaskResultRequestData.setThing3(thing3); wxTemplateTaskResultRequest.setData(wxTemplateTaskResultRequestData); // 4,发送消息给用户 String url = Constants.TEMPLATE_URL.replace("ACCESS_TOKEN", accessToken); String result = HttpUtils.sendPost(url, JsonUtils.toJsonString(wxTemplateTaskResultRequest)); log.info("任务数据提交结果通知模板消息返回数据:{}", result); } catch (Exception e) { log.error("任务数据提交结果通知失败;"); } } } ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/config/JacksonConfig.java
New file @@ -0,0 +1,35 @@ package com.ruoyi.system.utils.wx.config; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.time.format.DateTimeFormatter; /** * @author liheng * @ClassName LocalDateTimeSerializerConfig * @Description * @date 2020-09-22 11:22 */ @Configuration public class JacksonConfig { public static final String dateFormat = "yyyy-MM-dd"; public static final String dateTimeFormat = "yyyy-MM-dd HH:mm:ss"; @Bean public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { return builder -> { // Long型精度丢失问题 // builder.serializerByType(Long.TYPE, ToStringSerializer.instance); builder.serializerByType(Long.class, ToStringSerializer.instance); // 时间格式化 builder.simpleDateFormat(dateTimeFormat); builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern(dateFormat))); builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat))); }; } } ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/model/WeixinProperties.java
@@ -37,6 +37,20 @@ * @return secret ID */ private String secretId; /** * 任务驳回通知 || 任务审核通过通知 * * @return secret ID */ private String taskResultTemplateId; public String getTaskResultTemplateId() { return taskResultTemplateId; } public void setTaskResultTemplateId(String taskResultTemplateId) { this.taskResultTemplateId = taskResultTemplateId; } public String getSecretId() { return secretId; ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletUserEncrypteData.java
@@ -15,6 +15,4 @@ private String rawData; private String signature; private String code; @ApiModelProperty(value = "邀请用户id") private Long inviteUserId; } ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/template/Template.java
New file @@ -0,0 +1,19 @@ package com.ruoyi.system.utils.wx.template; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.io.Serializable; /** * @Description 微信消息模板参数实体 * @Author xiaochen * @Date 2021/11/8 10:30 */ @Data public class Template implements Serializable{ @ApiModelProperty(value = "消息内容") private String value; } ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/template/wxTemplateTaskReturn/WxTemplateTaskResultRequest.java
New file @@ -0,0 +1,22 @@ package com.ruoyi.system.utils.wx.template.wxTemplateTaskReturn; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.io.Serializable; @Data @ApiModel(value = "任务驳回消息模板配置") public class WxTemplateTaskResultRequest implements Serializable { @ApiModelProperty(value = "用户openid") private String touser; @ApiModelProperty(value = "消息模板id") private String template_id; @ApiModelProperty(value = "消息模板data数据") private WxTemplateTaskResultRequestData data; } ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/template/wxTemplateTaskReturn/WxTemplateTaskResultRequestData.java
New file @@ -0,0 +1,23 @@ package com.ruoyi.system.utils.wx.template.wxTemplateTaskReturn; import com.ruoyi.system.utils.wx.template.Template; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.io.Serializable; @Data @ApiModel(value = "任务驳回消息模板配置数据") public class WxTemplateTaskResultRequestData implements Serializable { @ApiModelProperty(value = "任务名称") private Template thing1; @ApiModelProperty(value = "审核结果") private Template phrase4; @ApiModelProperty(value = "审核人") private Template thing3; } ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxAppletTools.java
@@ -21,7 +21,7 @@ */ @Slf4j public class WxAppletTools { private final static String ACCESSTOKEN_CACHE_KEY = "accessToken"; private final static String ACCESSTOKEN_CACHE_KEY = "accessToken:"; /** * 请求参数 * 属性 类型 默认值 必填 说明 @@ -103,8 +103,8 @@ /** * @return */ public String getAccessToken(String version) { String accessToken = redisService.getCacheObject(ACCESSTOKEN_CACHE_KEY + version); public String getAccessToken() { String accessToken = redisService.getCacheObject(ACCESSTOKEN_CACHE_KEY); if (StringUtils.hasLength(accessToken)) { return accessToken; } @@ -116,7 +116,7 @@ // 抛出错误 throw new WxException(accessTokenRespBody.getErrorCode() + ":" + accessTokenRespBody.getErrorMsg()); } redisService.setCacheObject(ACCESSTOKEN_CACHE_KEY + version, accessTokenRespBody.getAccessToken(), 7200L, TimeUnit.SECONDS); redisService.setCacheObject(ACCESSTOKEN_CACHE_KEY, accessTokenRespBody.getAccessToken(), 7200L, TimeUnit.SECONDS); return accessTokenRespBody.getAccessToken(); } ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
@@ -28,6 +28,7 @@ <result property="businessDeptId" column="business_dept_id" /> <result property="roleName" column="role_name" /> <result property="code" column="code" /> <result property="openId" column="openId" /> <result property="deptType" column="deptType" /> <association property="dept" javaType="SysDept" resultMap="deptResult" /> <collection property="roles" javaType="java.util.List" resultMap="RoleResult" /> @@ -407,6 +408,7 @@ <if test="deptType != null">deptType=#{deptType},</if> <if test="templateId != null">templateId=#{templateId},</if> <if test="code != null and code != ''">code = #{code},</if> <if test="openId != null and openId != ''">openId = #{openId},</if> update_time = sysdate() </set>