From e0fc346f261f1549bb26db2ce601889d24015d9e Mon Sep 17 00:00:00 2001 From: wangw <1594593906@qq.com> Date: Mon, 20 Oct 2025 14:55:06 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=AC=E4=BC=97=E5=8F=B7=E8=AE=A2=E9=98=85?= =?UTF-8?q?=20=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/czg/controller/NotifyController.java | 30 +++++ .../src/main/resources/application.yml | 5 +- .../czg/account/dto/auth/LoginTokenDTO.java | 1 + .../java/com/czg/account/entity/UserInfo.java | 9 +- .../czg/system/enums/SysParamCodeEnum.java | 44 ++++++- .../impl/UserAuthorizationServiceImpl.java | 25 +++- .../service/account/util/AcAccountUtil.java | 124 ++++++++++++++++++ 7 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 cash-api/account-server/src/main/java/com/czg/controller/NotifyController.java create mode 100644 cash-service/account-service/src/main/java/com/czg/service/account/util/AcAccountUtil.java diff --git a/cash-api/account-server/src/main/java/com/czg/controller/NotifyController.java b/cash-api/account-server/src/main/java/com/czg/controller/NotifyController.java new file mode 100644 index 000000000..63d00ef89 --- /dev/null +++ b/cash-api/account-server/src/main/java/com/czg/controller/NotifyController.java @@ -0,0 +1,30 @@ +package com.czg.controller; + +import com.czg.account.service.UserInfoService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 公众号 通知 + * @author ww + * @description + */ +@Slf4j +@RestController +@RequestMapping("/notify") +public class NotifyController { + + private static final String SUCCESS = "SUCCESS"; + + @Resource + private UserInfoService userInfoService; + + @RequestMapping + public String notify(@RequestBody String str) { + log.info("公众号 通知:{}", str); + return SUCCESS; + } +} diff --git a/cash-api/product-server/src/main/resources/application.yml b/cash-api/product-server/src/main/resources/application.yml index b923d51f4..b1e265671 100644 --- a/cash-api/product-server/src/main/resources/application.yml +++ b/cash-api/product-server/src/main/resources/application.yml @@ -13,7 +13,10 @@ wx: secrete: 8492a7e8d55bbb1b57f5c8276ea1add0 operationMsgTmpId: wFdoUG-dUT7bDRHq8bMJD9CF5TjyH9x_uJQgQByZqHg warnMsgTmpId: C08OUr80x6wGmUN1zpFhSQ3Sv7VF5vksdZigiEx2pD0 - + # 公众号 + ac: + appId: wx1fb600d0f5ea6279 + secrete: b4c0534c9b5e6c84a7fe5c2078dff876 logging: config: classpath:logback.xml diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/dto/auth/LoginTokenDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/account/dto/auth/LoginTokenDTO.java index a6c93e8b3..3e4d8b292 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/account/dto/auth/LoginTokenDTO.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/account/dto/auth/LoginTokenDTO.java @@ -11,5 +11,6 @@ import lombok.Data; @Data public class LoginTokenDTO { private String token; + private String followIndex; private UserInfo userInfo; } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/entity/UserInfo.java b/cash-common/cash-common-service/src/main/java/com/czg/account/entity/UserInfo.java index ae9a64a57..92d60bec7 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/account/entity/UserInfo.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/account/entity/UserInfo.java @@ -19,7 +19,6 @@ import java.time.LocalDateTime; * @since 2025-02-11 */ @Data - @NoArgsConstructor @AllArgsConstructor @Table("tb_user_info") @@ -63,6 +62,10 @@ public class UserInfo implements Serializable { * 微信 openid */ private String wechatOpenId; + /** + * 微信公众号 openid + */ + private String wechatAcOpenId; /** * 支付宝 openid @@ -83,6 +86,10 @@ public class UserInfo implements Serializable { * 登录密码 */ private String password; + // 微信公众号二维码有效时间 + private LocalDateTime acQrcodeValidTime; + // 微信公众号二维码 + private String wechatAcQrcode; /** * 支付密码 diff --git a/cash-common/cash-common-service/src/main/java/com/czg/system/enums/SysParamCodeEnum.java b/cash-common/cash-common-service/src/main/java/com/czg/system/enums/SysParamCodeEnum.java index c1ce3f9cc..c6e7a753a 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/system/enums/SysParamCodeEnum.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/system/enums/SysParamCodeEnum.java @@ -9,17 +9,57 @@ import lombok.Getter; @Getter public enum SysParamCodeEnum { + + // 微信公众号(用户订阅相关) + WECHAT_AC_SECRETE("wechat_ac_secrete", "用户订阅公众号secret"), + WECHAT_AC_APPID("wechat_ac_appid", "用户订阅公众号appid"), + + // 公众号关注位置 + FOLLOW_INDEX("follow_index", "公众号关注位置 mine-我的 order-订单 eat-就餐"), + + // 商家配置 + AC_DAY_COUNT("ac_day_count", "商家每日可创建次数"), + + // 短信相关 + SMS_FEE("sms_fee", "短信费用"), + ALI_SMS_KEY("ali_sms_key", "阿里云短信key"), + ALI_SMS_TEMPLATE_CODE("ali_sms_template_code", "阿里云短信模板"), + ALI_SMS_SECRET("ali_sms_secret", "阿里云短信secret"), + + // 微信相关(公众号/小程序) + WX_ACCOUNT_APP_ID("wx_account_app_id", "微信公众号appId"), + WX_ACCOUNT_SECRETE("wx_account_secrete", "微信公众号密钥"), + WX_MINI_APP_ID("wx_mini_app_id", "微信小程序appId"), + WX_MINI_SECRETE("wx_mini_secrete", "微信小程序密钥"), + + // 页面地址相关 + WX_MINI_VIP_URL("wx_mini_vip_url", "小程序会员页面地址"), + TABLE_CODE_URL("table_code_url", "桌码生成路径"), + CALL_PAGE_URL("call_page_url", "叫号页面地址"), SHOP_ORDER_PAY_BASE_URL("shop_order_pay_base_url", "店铺订单支付BaseUrl"), + + // 支付相关(超掌柜) PAY_CZG_DOMAIN("pay_czg_domain", "超掌柜支付域名"), PAY_CZG_NOTIFY_URL("pay_czg_notify_url", "超掌柜支付回调地址"), PAY_CZG_REFUND_NOTIFY_URL("pay_czg_refund_notify_url", "超掌柜退款回调地址"), + + // 阿里云OSS相关 ALI_OSS_ACCESS_KEY("ali_oss_access_key", "阿里云oss_ACCESS_KEY"), ALI_OSS_ACCESS_SECRET("ali_oss_access_secret", "阿里云oss_secret"), ALI_OSS_ENDPOINT("ali_oss_endpoint", "阿里云endpoint"), ALI_OSS_ROLE_ARN("ali_oss_role_arn", "阿里云roleArn"), - WX_MINI_VIP_URL("wx_mini_vip_url", "小程序会员页面地址"), - TABLE_CODE_URL("table_code_url", "桌码生成路径"), + // 支付宝相关(小程序/网页) + ALI_MINI_PUBLIC_KEY("ali_mini_public_key", "支付宝小程序公钥"), + ALI_MINI_PRIVATE_KEY("ali_mini_private_key", "支付宝小程序支付私钥"), + ALI_MINI_APP_ID("ali_mini_app_id", "支付宝小程序id"), + ALI_GATEWAY("ali_gateway", "支付宝网关"), + ALI_ENCRYPT_KEY("ali_encrypt_key", "阿里AES加密串"), + ALI_ACCOUNT_PUBLIC_KEY("ali_account_public_key", "支付宝网页公钥"), + ALI_ACCOUNT_PRIVATE_KEY("ali_account_private_key", "支付宝网页私钥"), + ALI_ACCOUNT_APP_ID("ali_account_app_id", "支付宝网页appid"), + // 平台信息 + PLATE_NAME("plate_name", "平台名称") ; diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/UserAuthorizationServiceImpl.java b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/UserAuthorizationServiceImpl.java index 13e1f875e..bcd873dcc 100644 --- a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/UserAuthorizationServiceImpl.java +++ b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/UserAuthorizationServiceImpl.java @@ -20,12 +20,17 @@ import com.czg.exception.ApiNotPrintException; import com.czg.exception.CzgException; import com.czg.sa.MyStpLogic; import com.czg.sa.StpKit; +import com.czg.service.account.util.AcAccountUtil; import com.czg.service.account.util.AlipayUtil; import com.czg.service.account.util.WechatAuthUtil; +import com.czg.system.enums.SysParamCodeEnum; +import com.czg.system.service.SysParamsService; import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.stereotype.Service; import javax.annotation.Resource; +import java.time.LocalDateTime; /** * @author Administrator @@ -36,9 +41,13 @@ public class UserAuthorizationServiceImpl implements UserAuthorizationService { @Resource private WechatAuthUtil wechatAuthUtil; @Resource + private AcAccountUtil acAccountUtil; + @Resource private AlipayUtil alipayUtil; @Resource private UserInfoService userInfoService; + @DubboReference + private SysParamsService paramsService; @Override public String getOpenId(String code, String source) { @@ -117,8 +126,22 @@ public class UserAuthorizationServiceImpl implements UserAuthorizationService { userInfo.setLastLoginTime(DateUtil.date().toLocalDateTime()); userInfoService.saveOrUpdate(userInfo); + initAc(userInfo); // StpKit.USER.login(userInfo.getId()); StpKit.USER.login(userInfo.getId(), openId, null, null, null, MyStpLogic.LoginType.USER, false, "userMini", false); - return new LoginTokenDTO(StpKit.USER.getTokenValue(), userInfo); + String followIndex = paramsService.getSysParamValue(SysParamCodeEnum.FOLLOW_INDEX.getCode()); + return new LoginTokenDTO(StpKit.USER.getTokenValue(), followIndex, userInfo); + } + + /** + * 初始化用户微信公众号二维码\ + */ + private void initAc(UserInfo userInfo) { + if (StrUtil.isBlank(userInfo.getWechatAcOpenId()) && + (userInfo.getAcQrcodeValidTime() == null || userInfo.getAcQrcodeValidTime().isBefore(LocalDateTime.now()))) { + userInfo.setWechatAcQrcode(acAccountUtil.createQrCode(userInfo.getId())); + userInfo.setAcQrcodeValidTime(LocalDateTime.now().plusDays(29)); + userInfoService.updateById(userInfo); + } } } diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/util/AcAccountUtil.java b/cash-service/account-service/src/main/java/com/czg/service/account/util/AcAccountUtil.java new file mode 100644 index 000000000..f3a979959 --- /dev/null +++ b/cash-service/account-service/src/main/java/com/czg/service/account/util/AcAccountUtil.java @@ -0,0 +1,124 @@ +package com.czg.service.account.util; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSONObject; +import com.czg.service.RedisService; +import com.czg.system.service.SysParamsService; +import jakarta.annotation.Resource; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.LinkedHashMap; + +/** + * 微信公众号 + * + * @author ww + */ +@Data +@Slf4j +@Component +public class AcAccountUtil { + @Resource + @Lazy + private RedisService redisService; + @DubboReference + private SysParamsService paramsService; + + static LinkedHashMap linkedHashMap = new LinkedHashMap<>(); + + static { + linkedHashMap.put("40001", "获取 access_token 时 AppSecret 错误,或者 access_token 无效。请开发者认真比对 AppSecret 的正确性,或查看是否正在为恰当的公众号调用接口"); + linkedHashMap.put("40003", "不合法的 OpenID ,请开发者确认 OpenID (该用户)是否已关注公众号,或是否是其他公众号的 OpenID"); + linkedHashMap.put("40014", "不合法的 access_token ,请开发者认真比对 access_token 的有效性(如是否过期),或查看是否正在为恰当的公众号调用接口"); + linkedHashMap.put("40037", "不合法的 template_id"); + linkedHashMap.put("43101", "用户未订阅消息"); + linkedHashMap.put("43107", "订阅消息能力封禁"); + linkedHashMap.put("43108", "并发下发消息给同一个粉丝"); + linkedHashMap.put("45168", "命中敏感词"); + linkedHashMap.put("47003", "参数错误"); + } + + + /** + * 获取临时二维码用于关注公众号 30天(即2592000秒)后过期 + */ + public String createQrCode(Long userId) { + String accessToken = Convert.toStr(redisService.get("accessToken")); + if (StrUtil.isNotEmpty(accessToken)) { + return accessToken; + } + JSONObject bodyJson = new JSONObject(); + //二维码有效时间(秒),最大2592000,仅临时二维码需要 + bodyJson.put("expire_seconds", "2592000"); + //二维码类型:QR_SCENE(临时整型)/QR_STR_SCENE(临时字符串)/QR_LIMIT_SCENE(永久整型)/QR_LIMIT_STR_SCENE(永久字符串) + bodyJson.put("action_name", "QR_SCENE"); + JSONObject actionInfo = new JSONObject(); + JSONObject scene = new JSONObject(); + scene.put("scene_id", userId); + actionInfo.put("scene", scene); + bodyJson.put("action_info", actionInfo); + + String resp = HttpUtil.post(StrUtil.format("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={}", getAccessToken()), bodyJson); + /** + * { + * "ticket": "gQH47joAAAAAAAAAASxodHRwOi8vd2VpeGlu...", + * "expire_seconds": 60, + * "url": "http://weixin.qq.com/q/kZgfwMTm72WWPkovabbI" + * } + */ + JSONObject respInfo = JSONObject.parseObject(resp); + return respInfo.get("url").toString(); + } + + + public String getAccessToken() { + String accessToken = Convert.toStr(redisService.get("wx:ac:AccessToken")); + if (StrUtil.isNotEmpty(accessToken)) { + return accessToken; + } + String acAppId = paramsService.getSysParamValue("wechat_ac_appid"); + String acSecrete = paramsService.getSysParamValue("wechat_ac_secrete"); + String resp = HttpUtil.get(StrUtil.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}", acAppId, acSecrete)); + JSONObject respInfo = JSONObject.parseObject(resp); + if (!respInfo.containsKey("access_token")) { + log.warn("公众号获取token失败, 响应内容: {}", resp); + throw new RuntimeException(resp); + } + accessToken = respInfo.getString("access_token"); + int expiresIn = respInfo.getInteger("expires_in"); + redisService.set("wx:ac:AccessToken", accessToken, expiresIn - 10); + return accessToken; + } + + public static void main(String[] args) { + String resp = HttpUtil.get(StrUtil.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}", + "wx1fb600d0f5ea6279", "b4c0534c9b5e6c84a7fe5c2078dff876")); + JSONObject respInfo = JSONObject.parseObject(resp); + if (!respInfo.containsKey("access_token")) { + log.warn("公众号获取token失败, 响应内容: {}", resp); + throw new RuntimeException(resp); + } + String accessToken = respInfo.getString("access_token"); + JSONObject bodyJson = new JSONObject(); + //二维码有效时间(秒),最大2592000,仅临时二维码需要 + bodyJson.put("expire_seconds", "2592000"); + //二维码类型:QR_SCENE(临时整型)/QR_STR_SCENE(临时字符串)/QR_LIMIT_SCENE(永久整型)/QR_LIMIT_STR_SCENE(永久字符串) + bodyJson.put("action_name", "QR_SCENE"); + JSONObject actionInfo = new JSONObject(); + JSONObject scene = new JSONObject(); + scene.put("scene_id", 3); + actionInfo.put("scene", scene); + bodyJson.put("action_info", actionInfo); + System.out.println(bodyJson); + + String resps = HttpUtil.post(StrUtil.format("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={}", accessToken), JSONObject.toJSONString(bodyJson)); + JSONObject respInfos = JSONObject.parseObject(resps); + System.out.println(respInfos); + } +}