diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java index 6c365ca..ff22eda 100644 --- a/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java @@ -69,6 +69,7 @@ public class LoginController { @GetMapping("/getConfig") public ResponseDTO getConfig() { ConfigDTO configDTO = loginService.getConfig(); + configDTO.setRes("0"); return ResponseDTO.ok(configDTO); } diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/dto/ConfigDTO.java b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/dto/ConfigDTO.java index f82ed4d..57f9243 100644 --- a/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/dto/ConfigDTO.java +++ b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/dto/ConfigDTO.java @@ -15,4 +15,5 @@ public class ConfigDTO { private Map> dictionary; + private String res; } diff --git a/agileboot-api/src/main/java/com/agileboot/api/controller/PaymentController.java b/agileboot-api/src/main/java/com/agileboot/api/controller/PaymentController.java index 569693f..b1e2d14 100644 --- a/agileboot-api/src/main/java/com/agileboot/api/controller/PaymentController.java +++ b/agileboot-api/src/main/java/com/agileboot/api/controller/PaymentController.java @@ -1,8 +1,13 @@ package com.agileboot.api.controller; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.net.URLDecoder; +import cn.hutool.http.HttpUtil; import cn.hutool.json.JSONUtil; +import com.agileboot.common.constant.WeixinConstants; import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.common.exception.error.ErrorCode.Client; import com.agileboot.common.utils.OpenSignUtil; import com.agileboot.domain.shop.payment.dto.PaymentCallbackRequest; import java.nio.charset.StandardCharsets; @@ -13,15 +18,19 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.*; import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; @Slf4j @RestController +@CrossOrigin(origins = "*", allowedHeaders = "*") @RequiredArgsConstructor @RequestMapping("/api/payment") public class PaymentController { @@ -56,6 +65,29 @@ public class PaymentController { } } + @GetMapping("/getOpenId") + public ResponseDTO getOpenId(String code) { + try { + String url = String.format( + "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", + WeixinConstants.appid, WeixinConstants.secret, code); + + String response = HttpUtil.get(url); + log.info("微信https://api.weixin.qq.com/sns/oauth2/access_token接口返回: {}", response); + Map result = JSONUtil.toBean(response, HashMap.class); + + if (result.containsKey("errcode")) { + log.error("微信接口返回错误: {}", result); + return ResponseDTO.fail(new ApiException(Client.COMMON_REQUEST_PARAMETERS_INVALID, "无效的code参数")); + } + + return ResponseDTO.ok(String.valueOf(result.get("openid"))); + } catch (RestClientException e) { + log.error("获取openid失败", e); + return ResponseDTO.fail(new ApiException(Client.COMMON_REQUEST_PARAMETERS_INVALID, "微信服务调用失败")); + } + } + private PaymentCallbackRequest parseCallbackRequest(String requestBody) { // 实现将URL参数解析为PaymentCallbackRequest // 示例实现(需要根据实际参数格式调整): diff --git a/agileboot-api/src/main/java/com/agileboot/api/controller/ShopController.java b/agileboot-api/src/main/java/com/agileboot/api/controller/ShopController.java index f9b8dfc..07f198c 100644 --- a/agileboot-api/src/main/java/com/agileboot/api/controller/ShopController.java +++ b/agileboot-api/src/main/java/com/agileboot/api/controller/ShopController.java @@ -29,6 +29,6 @@ public class ShopController { List categoryList = categoryApplicationService.getCategoryAll(); - return ResponseDTO.ok(new ShopGoodsResponse(goodsList, categoryList)); + return ResponseDTO.ok(new ShopGoodsResponse(goodsList, categoryList, "0")); } } \ No newline at end of file diff --git a/agileboot-api/src/main/java/com/agileboot/api/response/ShopGoodsResponse.java b/agileboot-api/src/main/java/com/agileboot/api/response/ShopGoodsResponse.java index a867a20..05e24a6 100644 --- a/agileboot-api/src/main/java/com/agileboot/api/response/ShopGoodsResponse.java +++ b/agileboot-api/src/main/java/com/agileboot/api/response/ShopGoodsResponse.java @@ -12,4 +12,5 @@ import lombok.Data; public class ShopGoodsResponse { private List goodsList; private List categoryList; + private String res; } diff --git a/agileboot-common/lib/commons-codec-1.9.jar b/agileboot-common/lib/commons-codec-1.9.jar new file mode 100644 index 0000000..ef35f1c Binary files /dev/null and b/agileboot-common/lib/commons-codec-1.9.jar differ diff --git a/agileboot-common/pom.xml b/agileboot-common/pom.xml index b831f74..252dd62 100644 --- a/agileboot-common/pom.xml +++ b/agileboot-common/pom.xml @@ -188,6 +188,13 @@ swagger-annotations + + commons-codec + commons-codec + 1.9 + system + ${project.basedir}/lib/commons-codec-1.9.jar + diff --git a/agileboot-common/src/main/java/com/agileboot/common/constant/WeixinConstants.java b/agileboot-common/src/main/java/com/agileboot/common/constant/WeixinConstants.java new file mode 100644 index 0000000..2f3f377 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/constant/WeixinConstants.java @@ -0,0 +1,6 @@ +package com.agileboot.common.constant; + +public class WeixinConstants { + public static String appid = "wx04e357a5a0900f24"; + public static String secret = "2a5a8b6ad3654a05f9fdd36524279a50"; +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/AesException.java b/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/AesException.java new file mode 100644 index 0000000..be6bf9e --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/AesException.java @@ -0,0 +1,59 @@ +package com.agileboot.common.utils.weixin.aes; + +@SuppressWarnings("serial") +public class AesException extends Exception { + + public final static int OK = 0; + public final static int ValidateSignatureError = -40001; + public final static int ParseXmlError = -40002; + public final static int ComputeSignatureError = -40003; + public final static int IllegalAesKey = -40004; + public final static int ValidateCorpidError = -40005; + public final static int EncryptAESError = -40006; + public final static int DecryptAESError = -40007; + public final static int IllegalBuffer = -40008; + //public final static int EncodeBase64Error = -40009; + //public final static int DecodeBase64Error = -40010; + //public final static int GenReturnXmlError = -40011; + + private int code; + + private static String getMessage(int code) { + switch (code) { + case ValidateSignatureError: + return "签名验证错误"; + case ParseXmlError: + return "xml解析失败"; + case ComputeSignatureError: + return "sha加密生成签名失败"; + case IllegalAesKey: + return "SymmetricKey非法"; + case ValidateCorpidError: + return "corpid校验失败"; + case EncryptAESError: + return "aes加密失败"; + case DecryptAESError: + return "aes解密失败"; + case IllegalBuffer: + return "解密后得到的buffer非法"; +// case EncodeBase64Error: +// return "base64加密错误"; +// case DecodeBase64Error: +// return "base64解密错误"; +// case GenReturnXmlError: +// return "xml生成失败"; + default: + return null; // cannot be + } + } + + public int getCode() { + return code; + } + + AesException(int code) { + super(getMessage(code)); + this.code = code; + } + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/ByteGroup.java b/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/ByteGroup.java new file mode 100644 index 0000000..be7fc42 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/ByteGroup.java @@ -0,0 +1,26 @@ +package com.agileboot.common.utils.weixin.aes; + +import java.util.ArrayList; + +class ByteGroup { + ArrayList byteContainer = new ArrayList(); + + public byte[] toBytes() { + byte[] bytes = new byte[byteContainer.size()]; + for (int i = 0; i < byteContainer.size(); i++) { + bytes[i] = byteContainer.get(i); + } + return bytes; + } + + public ByteGroup addBytes(byte[] bytes) { + for (byte b : bytes) { + byteContainer.add(b); + } + return this; + } + + public int size() { + return byteContainer.size(); + } +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/PKCS7Encoder.java b/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/PKCS7Encoder.java new file mode 100644 index 0000000..ccff1d6 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/PKCS7Encoder.java @@ -0,0 +1,66 @@ +/** + * 对企业微信发送给企业后台的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +package com.agileboot.common.utils.weixin.aes; +import java.nio.charset.Charset; +import java.util.Arrays; + +/** + * 提供基于PKCS7算法的加解密接口. + */ +class PKCS7Encoder { + static Charset CHARSET = Charset.forName("utf-8"); + static int BLOCK_SIZE = 32; + + /** + * 获得对明文进行补位填充的字节. + * + * @param count 需要进行填充补位操作的明文字节个数 + * @return 补齐用的字节数组 + */ + static byte[] encode(int count) { + // 计算需要填充的位数 + int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); + if (amountToPad == 0) { + amountToPad = BLOCK_SIZE; + } + // 获得补位所用的字符 + char padChr = chr(amountToPad); + String tmp = new String(); + for (int index = 0; index < amountToPad; index++) { + tmp += padChr; + } + return tmp.getBytes(CHARSET); + } + + /** + * 删除解密后明文的补位字符 + * + * @param decrypted 解密后的明文 + * @return 删除补位字符后的明文 + */ + static byte[] decode(byte[] decrypted) { + int pad = (int) decrypted[decrypted.length - 1]; + if (pad < 1 || pad > 32) { + pad = 0; + } + return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); + } + + /** + * 将数字转化成ASCII码对应的字符,用于对明文进行补码 + * + * @param a 需要转化的数字 + * @return 转化得到的字符 + */ + static char chr(int a) { + byte target = (byte) (a & 0xFF); + return (char) target; + } + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/SHA1.java b/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/SHA1.java new file mode 100644 index 0000000..d30f244 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/SHA1.java @@ -0,0 +1,61 @@ +/** + * 对企业微信发送给企业后台的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +package com.agileboot.common.utils.weixin.aes; + +import java.security.MessageDigest; +import java.util.Arrays; + +/** + * SHA1 class + * + * 计算消息签名接口. + */ +class SHA1 { + + /** + * 用SHA1算法生成安全签名 + * @param token 票据 + * @param timestamp 时间戳 + * @param nonce 随机字符串 + * @param encrypt 密文 + * @return 安全签名 + * @throws AesException + */ + public static String getSHA1(String token, String timestamp, String nonce, String encrypt) throws AesException + { + try { + String[] array = new String[] { token, timestamp, nonce, encrypt }; + StringBuffer sb = new StringBuffer(); + // 字符串排序 + Arrays.sort(array); + for (int i = 0; i < 4; i++) { + sb.append(array[i]); + } + String str = sb.toString(); + // SHA1签名生成 + MessageDigest md = MessageDigest.getInstance("SHA-1"); + md.update(str.getBytes()); + byte[] digest = md.digest(); + + StringBuffer hexstr = new StringBuffer(); + String shaHex = ""; + for (int i = 0; i < digest.length; i++) { + shaHex = Integer.toHexString(digest[i] & 0xFF); + if (shaHex.length() < 2) { + hexstr.append(0); + } + hexstr.append(shaHex); + } + return hexstr.toString(); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.ComputeSignatureError); + } + } +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/Sample.java b/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/Sample.java new file mode 100644 index 0000000..836469d --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/Sample.java @@ -0,0 +1,138 @@ +package com.agileboot.common.utils.weixin.aes; + +import java.io.StringReader; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import javax.xml.parsers.DocumentBuilderFactory; + +public class Sample { + + public static void main(String[] args) throws Exception { + String sToken = "QDG6eK"; + String sCorpID = "wx5823bf96d3bd56c7"; + String sEncodingAESKey = "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C"; + + WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID); + /* + ------------使用示例一:验证回调URL--------------- + *企业开启回调模式时,企业微信会向验证url发送一个get请求 + 假设点击验证时,企业收到类似请求: + * GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D + * HTTP/1.1 Host: qy.weixin.qq.com + + 接收到该请求时,企业应 1.解析出Get请求的参数,包括消息体签名(msg_signature),时间戳(timestamp),随机数字串(nonce)以及企业微信推送过来的随机加密字符串(echostr), + 这一步注意作URL解码。 + 2.验证消息体签名的正确性 + 3. 解密出echostr原文,将原文当作Get请求的response,返回给企业微信 + 第2,3步可以用企业微信提供的库函数VerifyURL来实现。 + + */ + // 解析出url上的参数值如下: + // String sVerifyMsgSig = HttpUtils.ParseUrl("msg_signature"); + String sVerifyMsgSig = "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3"; + // String sVerifyTimeStamp = HttpUtils.ParseUrl("timestamp"); + String sVerifyTimeStamp = "1409659589"; + // String sVerifyNonce = HttpUtils.ParseUrl("nonce"); + String sVerifyNonce = "263014780"; + // String sVerifyEchoStr = HttpUtils.ParseUrl("echostr"); + String sVerifyEchoStr = "P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ=="; + String sEchoStr; //需要返回的明文 + try { + sEchoStr = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp, + sVerifyNonce, sVerifyEchoStr); + System.out.println("verifyurl echostr: " + sEchoStr); + // 验证URL成功,将sEchoStr返回 + // HttpUtils.SetResponse(sEchoStr); + } catch (Exception e) { + //验证URL失败,错误原因请查看异常 + e.printStackTrace(); + } + + /* + ------------使用示例二:对用户回复的消息解密--------------- + 用户回复消息或者点击事件响应时,企业会收到回调消息,此消息是经过企业微信加密之后的密文以post形式发送给企业,密文格式请参考官方文档 + 假设企业收到企业微信的回调消息如下: + POST /cgi-bin/wxpush? msg_signature=477715d11cdb4164915debcba66cb864d751f3e6×tamp=1409659813&nonce=1372623149 HTTP/1.1 + Host: qy.weixin.qq.com + Content-Length: 613 + + + + + 企业收到post请求之后应该 1.解析出url上的参数,包括消息体签名(msg_signature),时间戳(timestamp)以及随机数字串(nonce) + 2.验证消息体签名的正确性。 + 3.将post请求的数据进行xml解析,并将标签的内容进行解密,解密出来的明文即是用户回复消息的明文,明文格式请参考官方文档 + 第2,3步可以用企业微信提供的库函数DecryptMsg来实现。 + */ + // String sReqMsgSig = HttpUtils.ParseUrl("msg_signature"); + String sReqMsgSig = "477715d11cdb4164915debcba66cb864d751f3e6"; + // String sReqTimeStamp = HttpUtils.ParseUrl("timestamp"); + String sReqTimeStamp = "1409659813"; + // String sReqNonce = HttpUtils.ParseUrl("nonce"); + String sReqNonce = "1372623149"; + // post请求的密文数据 + // sReqData = HttpUtils.PostData(); + String sReqData = ""; + + try { + String sMsg = wxcpt.DecryptMsg(sReqMsgSig, sReqTimeStamp, sReqNonce, sReqData); + System.out.println("after decrypt msg: " + sMsg); + // TODO: 解析出明文xml标签的内容进行处理 + // For example: + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + StringReader sr = new StringReader(sMsg); + InputSource is = new InputSource(sr); + Document document = db.parse(is); + + Element root = document.getDocumentElement(); + NodeList nodelist1 = root.getElementsByTagName("Content"); + String Content = nodelist1.item(0).getTextContent(); + System.out.println("Content:" + Content); + + } catch (Exception e) { + // TODO + // 解密失败,失败原因请查看异常 + e.printStackTrace(); + } + + /* + ------------使用示例三:企业回复用户消息的加密--------------- + 企业被动回复用户的消息也需要进行加密,并且拼接成密文格式的xml串。 + 假设企业需要回复用户的明文如下: + + + + 1348831860 + + + 1234567890123456 + 128 + + + 为了将此段明文回复给用户,企业应: 1.自己生成时间时间戳(timestamp),随机数字串(nonce)以便生成消息体签名,也可以直接用从企业微信的post url上解析出的对应值。 + 2.将明文加密得到密文。 3.用密文,步骤1生成的timestamp,nonce和企业在企业微信设定的token生成消息体签名。 4.将密文,消息体签名,时间戳,随机数字串拼接成xml格式的字符串,发送给企业。 + 以上2,3,4步可以用企业微信提供的库函数EncryptMsg来实现。 + */ + String sRespData = "13488318601234567890123456128"; + try{ + String sEncryptMsg = wxcpt.EncryptMsg(sRespData, sReqTimeStamp, sReqNonce); + System.out.println("after encrypt sEncrytMsg: " + sEncryptMsg); + // 加密成功 + // TODO: + // HttpUtils.SetResponse(sEncryptMsg); + } + catch(Exception e) + { + e.printStackTrace(); + // 加密失败 + } + + } +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/WXBizMsgCrypt.java b/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/WXBizMsgCrypt.java new file mode 100644 index 0000000..4c51c33 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/WXBizMsgCrypt.java @@ -0,0 +1,288 @@ +/** + * 对企业微信发送给企业后台的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +/** + * 针对org.apache.commons.codec.binary.Base64, + * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) + * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi + */ +package com.agileboot.common.utils.weixin.aes; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Random; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.codec.binary.Base64; + +/** + * 提供接收和推送给企业微信消息的加解密接口(UTF8编码的字符串). + *
    + *
  1. 第三方回复加密消息给企业微信
  2. + *
  3. 第三方收到企业微信发送的消息,验证消息的安全性,并对消息进行解密。
  4. + *
+ * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案 + *
    + *
  1. 在官方网站下载JCE无限制权限策略文件(JDK7的下载地址: + * http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
  2. + *
  3. 下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt
  4. + *
  5. 如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件
  6. + *
  7. 如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件
  8. + *
+ */ +public class WXBizMsgCrypt { + static Charset CHARSET = Charset.forName("utf-8"); + Base64 base64 = new Base64(); + byte[] aesKey; + String token; + String receiveid; + + /** + * 构造函数 + * @param token 企业微信后台,开发者设置的token + * @param encodingAesKey 企业微信后台,开发者设置的EncodingAESKey + * @param receiveid, 不同场景含义不同,详见文档 + * + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public WXBizMsgCrypt(String token, String encodingAesKey, String receiveid) throws AesException { + if (encodingAesKey.length() != 43) { + throw new AesException(AesException.IllegalAesKey); + } + + this.token = token; + this.receiveid = receiveid; + aesKey = Base64.decodeBase64(encodingAesKey + "="); + } + + // 生成4个字节的网络字节序 + byte[] getNetworkBytesOrder(int sourceNumber) { + byte[] orderBytes = new byte[4]; + orderBytes[3] = (byte) (sourceNumber & 0xFF); + orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF); + orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF); + orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF); + return orderBytes; + } + + // 还原4个字节的网络字节序 + int recoverNetworkBytesOrder(byte[] orderBytes) { + int sourceNumber = 0; + for (int i = 0; i < 4; i++) { + sourceNumber <<= 8; + sourceNumber |= orderBytes[i] & 0xff; + } + return sourceNumber; + } + + // 随机生成16位字符串 + String getRandomStr() { + String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + Random random = new Random(); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < 16; i++) { + int number = random.nextInt(base.length()); + sb.append(base.charAt(number)); + } + return sb.toString(); + } + + /** + * 对明文进行加密. + * + * @param text 需要加密的明文 + * @return 加密后base64编码的字符串 + * @throws AesException aes加密失败 + */ + String encrypt(String randomStr, String text) throws AesException { + ByteGroup byteCollector = new ByteGroup(); + byte[] randomStrBytes = randomStr.getBytes(CHARSET); + byte[] textBytes = text.getBytes(CHARSET); + byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length); + byte[] receiveidBytes = receiveid.getBytes(CHARSET); + + // randomStr + networkBytesOrder + text + receiveid + byteCollector.addBytes(randomStrBytes); + byteCollector.addBytes(networkBytesOrder); + byteCollector.addBytes(textBytes); + byteCollector.addBytes(receiveidBytes); + + // ... + pad: 使用自定义的填充方式对明文进行补位填充 + byte[] padBytes = PKCS7Encoder.encode(byteCollector.size()); + byteCollector.addBytes(padBytes); + + // 获得最终的字节流, 未加密 + byte[] unencrypted = byteCollector.toBytes(); + + try { + // 设置加密模式为AES的CBC模式 + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); + IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); + + // 加密 + byte[] encrypted = cipher.doFinal(unencrypted); + + // 使用BASE64对加密后的字符串进行编码 + String base64Encrypted = base64.encodeToString(encrypted); + + return base64Encrypted; + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.EncryptAESError); + } + } + + /** + * 对密文进行解密. + * + * @param text 需要解密的密文 + * @return 解密得到的明文 + * @throws AesException aes解密失败 + */ + String decrypt(String text) throws AesException { + byte[] original; + try { + // 设置解密模式为AES的CBC模式 + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES"); + IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16)); + cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); + + // 使用BASE64对密文进行解码 + byte[] encrypted = Base64.decodeBase64(text); + + // 解密 + original = cipher.doFinal(encrypted); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.DecryptAESError); + } + + String xmlContent, from_receiveid; + try { + // 去除补位字符 + byte[] bytes = PKCS7Encoder.decode(original); + + // 分离16位随机字符串,网络字节序和receiveid + byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); + + int xmlLength = recoverNetworkBytesOrder(networkOrder); + + xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET); + from_receiveid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), + CHARSET); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.IllegalBuffer); + } + + // receiveid不相同的情况 + if (!from_receiveid.equals(receiveid)) { + throw new AesException(AesException.ValidateCorpidError); + } + return xmlContent; + + } + + /** + * 将企业微信回复用户的消息加密打包. + *
    + *
  1. 对要发送的消息进行AES-CBC加密
  2. + *
  3. 生成安全签名
  4. + *
  5. 将消息密文和安全签名打包成xml格式
  6. + *
+ * + * @param replyMsg 企业微信待回复用户的消息,xml格式的字符串 + * @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp + * @param nonce 随机串,可以自己生成,也可以用URL参数的nonce + * + * @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串 + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String EncryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException { + // 加密 + String encrypt = encrypt(getRandomStr(), replyMsg); + + // 生成安全签名 + if (timeStamp == "") { + timeStamp = Long.toString(System.currentTimeMillis()); + } + + String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt); + + // System.out.println("发送给平台的签名是: " + signature[1].toString()); + // 生成发送的xml + String result = XMLParse.generate(encrypt, signature, timeStamp, nonce); + return result; + } + + /** + * 检验消息的真实性,并且获取解密后的明文. + *
    + *
  1. 利用收到的密文生成安全签名,进行签名验证
  2. + *
  3. 若验证通过,则提取xml中的加密消息
  4. + *
  5. 对消息进行解密
  6. + *
+ * + * @param msgSignature 签名串,对应URL参数的msg_signature + * @param timeStamp 时间戳,对应URL参数的timestamp + * @param nonce 随机串,对应URL参数的nonce + * @param postData 密文,对应POST请求的数据 + * + * @return 解密后的原文 + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String DecryptMsg(String msgSignature, String timeStamp, String nonce, String postData) + throws AesException { + + // 密钥,公众账号的app secret + // 提取密文 + Object[] encrypt = XMLParse.extract(postData); + + // 验证安全签名 + String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt[1].toString()); + + // 和URL中的签名比较是否相等 + // System.out.println("第三方收到URL中的签名:" + msg_sign); + // System.out.println("第三方校验签名:" + signature); + if (!signature.equals(msgSignature)) { + throw new AesException(AesException.ValidateSignatureError); + } + + // 解密 + String result = decrypt(encrypt[1].toString()); + return result; + } + + /** + * 验证URL + * @param msgSignature 签名串,对应URL参数的msg_signature + * @param timeStamp 时间戳,对应URL参数的timestamp + * @param nonce 随机串,对应URL参数的nonce + * @param echoStr 随机串,对应URL参数的echostr + * + * @return 解密之后的echostr + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String VerifyURL(String msgSignature, String timeStamp, String nonce, String echoStr) + throws AesException { + String signature = SHA1.getSHA1(token, timeStamp, nonce, echoStr); + + if (!signature.equals(msgSignature)) { + throw new AesException(AesException.ValidateSignatureError); + } + + String result = decrypt(echoStr); + return result; + } + +} \ No newline at end of file diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/XMLParse.java b/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/XMLParse.java new file mode 100644 index 0000000..dbdf322 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/weixin/aes/XMLParse.java @@ -0,0 +1,104 @@ +/** + * 对企业微信发送给企业后台的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +package com.agileboot.common.utils.weixin.aes; + +import java.io.StringReader; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +/** + * XMLParse class + * + * 提供提取消息格式中的密文及生成回复消息格式的接口. + */ +class XMLParse { + + /** + * 提取出xml数据包中的加密消息 + * @param xmltext 待提取的xml字符串 + * @return 提取出的加密消息字符串 + * @throws AesException + */ + public static Object[] extract(String xmltext) throws AesException { + Object[] result = new Object[3]; + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + + String FEATURE = null; + // This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented + // Xerces 2 only - http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl + FEATURE = "http://apache.org/xml/features/disallow-doctype-decl"; + dbf.setFeature(FEATURE, true); + + // If you can't completely disable DTDs, then at least do the following: + // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities + // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities + // JDK7+ - http://xml.org/sax/features/external-general-entities + FEATURE = "http://xml.org/sax/features/external-general-entities"; + dbf.setFeature(FEATURE, false); + + // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities + // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities + // JDK7+ - http://xml.org/sax/features/external-parameter-entities + FEATURE = "http://xml.org/sax/features/external-parameter-entities"; + dbf.setFeature(FEATURE, false); + + // Disable external DTDs as well + FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; + dbf.setFeature(FEATURE, false); + + // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + + // And, per Timothy Morgan: "If for some reason support for inline DOCTYPEs are a requirement, then + // ensure the entity settings are disabled (as shown above) and beware that SSRF attacks + // (http://cwe.mitre.org/data/definitions/918.html) and denial + // of service attacks (such as billion laughs or decompression bombs via "jar:") are a risk." + + // remaining parser logic + DocumentBuilder db = dbf.newDocumentBuilder(); + StringReader sr = new StringReader(xmltext); + InputSource is = new InputSource(sr); + Document document = db.parse(is); + + Element root = document.getDocumentElement(); + NodeList nodelist1 = root.getElementsByTagName("Encrypt"); + result[0] = 0; + result[1] = nodelist1.item(0).getTextContent(); + return result; + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.ParseXmlError); + } + } + + /** + * 生成xml消息 + * @param encrypt 加密后的消息密文 + * @param signature 安全签名 + * @param timestamp 时间戳 + * @param nonce 随机字符串 + * @return 生成的xml字符串 + */ + public static String generate(String encrypt, String signature, String timestamp, String nonce) { + + String format = "\n" + "\n" + + "\n" + + "%3$s\n" + "\n" + ""; + return String.format(format, encrypt, signature, timestamp, nonce); + + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenEntity.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenEntity.java new file mode 100644 index 0000000..94fd6a5 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenEntity.java @@ -0,0 +1,96 @@ +package com.agileboot.domain.qywx.accessToken.db; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import java.io.Serializable; +import java.util.Date; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 企业微信-access_token + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +@Getter +@Setter +@TableName("qy_access_token") +@ApiModel(value = "QyAccessTokenEntity对象", description = "企业微信-access_token") +public class QyAccessTokenEntity extends Model { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("主键编号") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @ApiModelProperty("操作序号") + @TableField("oper_id") + private Integer operId; + + @ApiModelProperty("模板ID") + @TableField("suiteId") + private String suiteId; + + @ApiModelProperty("应用名称") + @TableField("agent_name") + private String agentName; + + @ApiModelProperty("应用ID") + @TableField("agent_id") + private String agentId; + + @ApiModelProperty("授权方企业 corpid") + @TableField("corpid") + private String corpid; + + @ApiModelProperty("Secret(企业微信永久授权码 permanent_code)") + @TableField("secret") + private String secret; + + @ApiModelProperty("获取时间") + @TableField("gettoken_time") + private Date gettokenTime; + + @ApiModelProperty("获取到的凭证,最长为512字节") + @TableField("access_token") + private String accessToken; + + @ApiModelProperty("凭证的有效时间(秒) (正常情况下为7200秒(2小时))") + @TableField("expires_in") + private Integer expiresIn; + + @ApiModelProperty("出错返回码,为0表示成功,非0表示调用失败") + @TableField("errcode") + private String errcode; + + @ApiModelProperty("返回码提示语") + @TableField("errmsg") + private String errmsg; + + @ApiModelProperty("授权状态(0:未授权;1:已授权;2:已取消;)") + @TableField("auth_state") + private String authState; + + @ApiModelProperty("有效标志(1-有效,0-无效)") + @TableField("`enable`") + private String enable; + + @TableField("appid") + private String appid; + + + @Override + public Serializable pkVal() { + return this.id; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenMapper.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenMapper.java new file mode 100644 index 0000000..ffe02e1 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenMapper.java @@ -0,0 +1,15 @@ +package com.agileboot.domain.qywx.accessToken.db; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 企业微信-access_token Mapper 接口 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +public interface QyAccessTokenMapper extends BaseMapper { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenService.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenService.java new file mode 100644 index 0000000..e747bca --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenService.java @@ -0,0 +1,15 @@ +package com.agileboot.domain.qywx.accessToken.db; + +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 企业微信-access_token 服务类 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +public interface QyAccessTokenService extends IService { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenServiceImpl.java new file mode 100644 index 0000000..00e85f1 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenServiceImpl.java @@ -0,0 +1,17 @@ +package com.agileboot.domain.qywx.accessToken.db; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 企业微信-access_token 服务实现类 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +@Service +public class QyAccessTokenServiceImpl extends ServiceImpl implements QyAccessTokenService { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/auth/db/QyAuthEntity.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/auth/db/QyAuthEntity.java new file mode 100644 index 0000000..37e58a3 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/auth/db/QyAuthEntity.java @@ -0,0 +1,79 @@ +package com.agileboot.domain.qywx.auth.db; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import java.io.Serializable; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 企业微信-代开发应用模板授权 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +@Getter +@Setter +@TableName("qy_auth") +@ApiModel(value = "QyAuthEntity对象", description = "企业微信-代开发应用模板授权") +public class QyAuthEntity extends Model { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("主键编号") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @ApiModelProperty("操作序号") + @TableField("oper_id") + private Integer operId; + + @ApiModelProperty("第三方应用的SuiteId或者代开发应用模板id") + @TableField("SuiteId") + private String suiteId; + + @ApiModelProperty("临时授权码,最长为512字节。用于获取企业永久授权码。10分钟内有效") + @TableField("AuthCode") + private String authCode; + + @ApiModelProperty("信息类型(create_auth:授权成功通知;change_auth:变更授权通知;cancel_auth:取消授权通知;suite_ticket:推送suite_ticket)") + @TableField("InfoType") + private String infoType; + + @ApiModelProperty("时间戳") + @TableField("`TimeStamp`") + private String timeStamp; + + @ApiModelProperty("授权方的corpid") + @TableField("AuthCorpId") + private String authCorpId; + + @ApiModelProperty("构造授权链接指定的state参数") + @TableField("State") + private String state; + + @ApiModelProperty("额外信息,部分场景会携带,在其他文档中分别描述") + @TableField("ExtraInfo") + private String extraInfo; + + @ApiModelProperty("Ticket内容,最长为512字节") + @TableField("SuiteTicket") + private String suiteTicket; + + @TableField("appid") + private String appid; + + + @Override + public Serializable pkVal() { + return this.id; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/auth/db/QyAuthMapper.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/auth/db/QyAuthMapper.java new file mode 100644 index 0000000..fe9bdda --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/auth/db/QyAuthMapper.java @@ -0,0 +1,15 @@ +package com.agileboot.domain.qywx.auth.db; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 企业微信-代开发应用模板授权 Mapper 接口 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +public interface QyAuthMapper extends BaseMapper { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/auth/db/QyAuthService.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/auth/db/QyAuthService.java new file mode 100644 index 0000000..4a35e73 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/auth/db/QyAuthService.java @@ -0,0 +1,15 @@ +package com.agileboot.domain.qywx.auth.db; + +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 企业微信-代开发应用模板授权 服务类 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +public interface QyAuthService extends IService { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/auth/db/QyAuthServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/auth/db/QyAuthServiceImpl.java new file mode 100644 index 0000000..12d2396 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/auth/db/QyAuthServiceImpl.java @@ -0,0 +1,17 @@ +package com.agileboot.domain.qywx.auth.db; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 企业微信-代开发应用模板授权 服务实现类 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +@Service +public class QyAuthServiceImpl extends ServiceImpl implements QyAuthService { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoEntity.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoEntity.java new file mode 100644 index 0000000..e379eb9 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoEntity.java @@ -0,0 +1,154 @@ +package com.agileboot.domain.qywx.authCorpInfo.db; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import java.io.Serializable; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 企业微信-授权方企业信息 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +@Getter +@Setter +@TableName("qy_auth_corp_info") +@ApiModel(value = "QyAuthCorpInfoEntity对象", description = "企业微信-授权方企业信息") +public class QyAuthCorpInfoEntity extends Model { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("主键编号") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @ApiModelProperty("操作序号") + @TableField("oper_id") + private Integer operId; + + @ApiModelProperty("授权方企业微信id") + @TableField("corpid") + private String corpid; + + @ApiModelProperty("授权方企业名称,即企业简称") + @TableField("corp_name") + private String corpName; + + @ApiModelProperty("授权方企业类型,认证号:verified, 注册号:unverified") + @TableField("corp_type") + private String corpType; + + @ApiModelProperty("授权方企业方形头像") + @TableField("corp_square_logo_url") + private String corpSquareLogoUrl; + + @ApiModelProperty("授权方企业用户规模") + @TableField("corp_user_max") + private String corpUserMax; + + @ApiModelProperty("授权方企业的主体名称,即企业全称。") + @TableField("corp_full_name") + private String corpFullName; + + @ApiModelProperty("企业类型,1. 企业; 2. 政府以及事业单位; 3. 其他组织, 4.团队号") + @TableField("subject_type") + private String subjectType; + + @ApiModelProperty("认证到期时间") + @TableField("verified_end_time") + private String verifiedEndTime; + + @ApiModelProperty("授权企业在微信插件(原企业号)的二维码,可用于关注微信插件") + @TableField("corp_wxqrcode") + private String corpWxqrcode; + + @ApiModelProperty("企业规模。当企业未设置该属性时,值为空") + @TableField("corp_scale") + private String corpScale; + + @ApiModelProperty("企业所属行业。当企业未设置该属性时,值为空") + @TableField("corp_industry") + private String corpIndustry; + + @ApiModelProperty("企业所属子行业。当企业未设置该属性时,值为空") + @TableField("corp_sub_industry") + private String corpSubIndustry; + + @ApiModelProperty("授权方应用id") + @TableField("agentid") + private String agentid; + + @ApiModelProperty("授权方应用名字") + @TableField("agentname") + private String agentname; + + @ApiModelProperty("授权管理员的userid,可能为空") + @TableField("userid") + private String userid; + + @ApiModelProperty("授权管理员的open_userid,可能为空") + @TableField("open_userid") + private String openUserid; + + @ApiModelProperty("授权管理员的name,可能为空") + @TableField("`name`") + private String name; + + @TableField("avatar") + private String avatar; + + @ApiModelProperty("安装应用时,扫码或者授权链接中带的state值。详见state说明") + @TableField("state") + private String state; + + @ApiModelProperty("模板ID") + @TableField("suiteId") + private String suiteId; + + @ApiModelProperty("临时授权码") + @TableField("AuthCode") + private String authCode; + + @ApiModelProperty("企业微信永久授权码") + @TableField("permanent_code") + private String permanentCode; + + @ApiModelProperty("授权方企业信息(json字符串)") + @TableField("auth_corp_info") + private String authCorpInfo; + + @ApiModelProperty("授权状态(0:未授权;1:已授权;2:已取消;)") + @TableField("auth_state") + private String authState; + + @ApiModelProperty("代开发应用回调URL") + @TableField("url") + private String url; + + @ApiModelProperty("token回调") + @TableField("token") + private String token; + + @ApiModelProperty("encodingAESKey") + @TableField("encodingAESKey") + private String encodingAESKey; + + @TableField("appid") + private String appid; + + + @Override + public Serializable pkVal() { + return this.id; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoMapper.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoMapper.java new file mode 100644 index 0000000..d60e26f --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoMapper.java @@ -0,0 +1,15 @@ +package com.agileboot.domain.qywx.authCorpInfo.db; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 企业微信-授权方企业信息 Mapper 接口 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +public interface QyAuthCorpInfoMapper extends BaseMapper { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoService.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoService.java new file mode 100644 index 0000000..062ee73 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoService.java @@ -0,0 +1,15 @@ +package com.agileboot.domain.qywx.authCorpInfo.db; + +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 企业微信-授权方企业信息 服务类 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +public interface QyAuthCorpInfoService extends IService { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoServiceImpl.java new file mode 100644 index 0000000..d246a64 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoServiceImpl.java @@ -0,0 +1,17 @@ +package com.agileboot.domain.qywx.authCorpInfo.db; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 企业微信-授权方企业信息 服务实现类 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +@Service +public class QyAuthCorpInfoServiceImpl extends ServiceImpl implements QyAuthCorpInfoService { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/department/db/QyDepartmentEntity.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/department/db/QyDepartmentEntity.java new file mode 100644 index 0000000..c4587cc --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/department/db/QyDepartmentEntity.java @@ -0,0 +1,84 @@ +package com.agileboot.domain.qywx.department.db; + +import com.agileboot.common.core.base.BaseEntity; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 企业微信-部门信息 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +@Getter +@Setter +@TableName("qy_department") +@ApiModel(value = "QyDepartmentEntity对象", description = "企业微信-部门信息") +public class QyDepartmentEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("主键编号") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @ApiModelProperty("操作序号") + @TableField("oper_id") + private Integer operId; + + @ApiModelProperty("父部门id。根部门为1") + @TableField("parentid") + private String parentid; + + @ApiModelProperty("创建的部门id") + @TableField("department_id") + private String departmentId; + + @ApiModelProperty("部门名称") + @TableField("`name`") + private String name; + + @ApiModelProperty("英文名称") + @TableField("name_en") + private String nameEn; + + @ApiModelProperty("部门负责人的UserID") + @TableField("department_leader") + private String departmentLeader; + + @ApiModelProperty("在父部门中的次序值。order值大的排序靠前。值范围是[0, 2^32)") + @TableField("department_order") + private String departmentOrder; + + @ApiModelProperty("有效标志(1-有效,0-无效)") + @TableField("`enable`") + private String enable; + + @ApiModelProperty("企业微信id") + @TableField("corpid") + private String corpid; + + @ApiModelProperty("应用ID") + @TableField("appid") + private String appid; + + @ApiModelProperty("公司ID") + @TableField("cid") + private Integer cid; + + + @Override + public Serializable pkVal() { + return this.id; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/department/db/QyDepartmentMapper.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/department/db/QyDepartmentMapper.java new file mode 100644 index 0000000..f4dd6ab --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/department/db/QyDepartmentMapper.java @@ -0,0 +1,15 @@ +package com.agileboot.domain.qywx.department.db; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 企业微信-部门信息 Mapper 接口 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +public interface QyDepartmentMapper extends BaseMapper { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/department/db/QyDepartmentService.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/department/db/QyDepartmentService.java new file mode 100644 index 0000000..a1d0a00 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/department/db/QyDepartmentService.java @@ -0,0 +1,15 @@ +package com.agileboot.domain.qywx.department.db; + +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 企业微信-部门信息 服务类 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +public interface QyDepartmentService extends IService { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/department/db/QyDepartmentServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/department/db/QyDepartmentServiceImpl.java new file mode 100644 index 0000000..7e0bb96 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/department/db/QyDepartmentServiceImpl.java @@ -0,0 +1,17 @@ +package com.agileboot.domain.qywx.department.db; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 企业微信-部门信息 服务实现类 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +@Service +public class QyDepartmentServiceImpl extends ServiceImpl implements QyDepartmentService { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/message/db/QyMessageEntity.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/message/db/QyMessageEntity.java new file mode 100644 index 0000000..77707b6 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/message/db/QyMessageEntity.java @@ -0,0 +1,153 @@ +package com.agileboot.domain.qywx.message.db; + +import com.agileboot.common.core.base.BaseEntity; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 企业微信-消息推送 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +@Getter +@Setter +@TableName("qy_message") +@ApiModel(value = "QyMessageEntity对象", description = "企业微信-消息推送") +public class QyMessageEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("主键编号") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @ApiModelProperty("操作序号") + @TableField("oper_id") + private Integer operId; + + @ApiModelProperty("发送时间") + @TableField("send_time") + private Date sendTime; + + @ApiModelProperty("授权方企业 corpid") + @TableField("corpid") + private String corpid; + + @ApiModelProperty("应用ID") + @TableField("appid") + private String appid; + + @ApiModelProperty("指定接收消息的成员,成员ID列表(多个接收者用‘|’分隔,最多支持1000个)。 特殊情况:指定为\"@all\",则向该企业应用的全部成员发送") + @TableField("touser") + private String touser; + + @ApiModelProperty("指定接收消息的部门,部门ID列表,多个接收者用‘|’分隔,最多支持100个。 当touser为\"@all\"时忽略本参数") + @TableField("toparty") + private String toparty; + + @ApiModelProperty("指定接收消息的标签,标签ID列表,多个接收者用‘|’分隔,最多支持100个。 当touser为\"@all\"时忽略本参数") + @TableField("totag") + private String totag; + + @ApiModelProperty("消息类型(text)") + @TableField("msgtype") + private String msgtype; + + @ApiModelProperty("企业应用的id") + @TableField("agentid") + private String agentid; + + @ApiModelProperty("消息内容(JSON数据)") + @TableField("message") + private String message; + + @ApiModelProperty("表示是否是保密消息,0表示可对外分享,1表示不能分享且内容显示水印,默认为0") + @TableField("safe") + private String safe; + + @ApiModelProperty("表示是否开启id转译,0表示否,1表示是,默认0。仅第三方应用需要用到,企业自建应用可以忽略。") + @TableField("enable_id_trans") + private String enableIdTrans; + + @ApiModelProperty("表示是否开启重复消息检查,0表示否,1表示是,默认0") + @TableField("enable_duplicate_check") + private String enableDuplicateCheck; + + @ApiModelProperty("表示是否重复消息检查的时间间隔,默认1800s,最大不超过4小时") + @TableField("duplicate_check_interval") + private String duplicateCheckInterval; + + @ApiModelProperty("返回码") + @TableField("errcode") + private String errcode; + + @ApiModelProperty("对返回码的文本描述内容") + @TableField("errmsg") + private String errmsg; + + @ApiModelProperty("不合法的userid,不区分大小写,统一转为小写") + @TableField("invaliduser") + private String invaliduser; + + @ApiModelProperty("不合法的partyid") + @TableField("invalidparty") + private String invalidparty; + + @ApiModelProperty("不合法的标签id") + @TableField("invalidtag") + private String invalidtag; + + @ApiModelProperty("没有基础接口许可(包含已过期)的userid") + @TableField("unlicenseduser") + private String unlicenseduser; + + @ApiModelProperty("消息id,用于撤回应用消息") + @TableField("msgid") + private String msgid; + + @ApiModelProperty("仅消息类型为“按钮交互型”,“投票选择型”和“多项选择型”的模板卡片消息返回,应用可使用response_code调用更新模版卡片消息接口,72小时内有效,且只能使用一次") + @TableField("response_code") + private String responseCode; + + @ApiModelProperty("消息推送JSON内容") + @TableField("send_jsondata") + private String sendJsondata; + + @ApiModelProperty("撤回状态(0:未撤回;1:已撤回;)") + @TableField("recall_state") + private String recallState; + + @ApiModelProperty("撤回时间") + @TableField("recall_time") + private Date recallTime; + + @ApiModelProperty("撤回,返回码") + @TableField("recall_errcode") + private String recallErrcode; + + @ApiModelProperty("撤回,对返回码的文本描述内容") + @TableField("recall_errmsg") + private String recallErrmsg; + + @ApiModelProperty("公司ID") + @TableField("cid") + private Integer cid; + + + @Override + public Serializable pkVal() { + return this.id; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/message/db/QyMessageMapper.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/message/db/QyMessageMapper.java new file mode 100644 index 0000000..6217e66 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/message/db/QyMessageMapper.java @@ -0,0 +1,15 @@ +package com.agileboot.domain.qywx.message.db; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 企业微信-消息推送 Mapper 接口 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +public interface QyMessageMapper extends BaseMapper { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/message/db/QyMessageService.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/message/db/QyMessageService.java new file mode 100644 index 0000000..0f8a1e6 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/message/db/QyMessageService.java @@ -0,0 +1,15 @@ +package com.agileboot.domain.qywx.message.db; + +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 企业微信-消息推送 服务类 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +public interface QyMessageService extends IService { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/message/db/QyMessageServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/message/db/QyMessageServiceImpl.java new file mode 100644 index 0000000..d13d95c --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/message/db/QyMessageServiceImpl.java @@ -0,0 +1,17 @@ +package com.agileboot.domain.qywx.message.db; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 企业微信-消息推送 服务实现类 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +@Service +public class QyMessageServiceImpl extends ServiceImpl implements QyMessageService { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/template/db/QyTemplateEntity.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/template/db/QyTemplateEntity.java new file mode 100644 index 0000000..97142b8 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/template/db/QyTemplateEntity.java @@ -0,0 +1,100 @@ +package com.agileboot.domain.qywx.template.db; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import java.io.Serializable; +import java.util.Date; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 企业微信-代开发应用模板信息 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +@Getter +@Setter +@TableName("qy_template") +@ApiModel(value = "QyTemplateEntity对象", description = "企业微信-代开发应用模板信息") +public class QyTemplateEntity extends Model { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("主键编号") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @ApiModelProperty("操作序号") + @TableField("oper_id") + private Integer operId; + + @ApiModelProperty("服务商企业ID corpid") + @TableField("corpid") + private String corpid; + + @ApiModelProperty("服务商名称") + @TableField("corp_name") + private String corpName; + + @ApiModelProperty("模板ID") + @TableField("suiteId") + private String suiteId; + + @ApiModelProperty("模板Secret") + @TableField("secret") + private String secret; + + @ApiModelProperty("代开发模板回调URL") + @TableField("url") + private String url; + + @ApiModelProperty("Token") + @TableField("token") + private String token; + + @ApiModelProperty("EncodingAESKey") + @TableField("encodingAESKey") + private String encodingAESKey; + + @ApiModelProperty("Ticket内容,最长为512字节") + @TableField("SuiteTicket") + private String suiteTicket; + + @ApiModelProperty("获取时间") + @TableField("gettoken_time") + private Date gettokenTime; + + @ApiModelProperty("第三方或者代开发应用access_token,最长为512字节") + @TableField("suite_access_token") + private String suiteAccessToken; + + @ApiModelProperty("凭证的有效时间(秒)") + @TableField("expires_in") + private Integer expiresIn; + + @ApiModelProperty("日志输出状态(0:不输出;1:输出;)") + @TableField("log_state") + private String logState; + + @ApiModelProperty("有效标志(1-有效,0-无效)") + @TableField("`enable`") + private String enable; + + @TableField("appid") + private String appid; + + + @Override + public Serializable pkVal() { + return this.id; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/template/db/QyTemplateMapper.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/template/db/QyTemplateMapper.java new file mode 100644 index 0000000..1628ce7 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/template/db/QyTemplateMapper.java @@ -0,0 +1,15 @@ +package com.agileboot.domain.qywx.template.db; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 企业微信-代开发应用模板信息 Mapper 接口 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +public interface QyTemplateMapper extends BaseMapper { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/template/db/QyTemplateService.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/template/db/QyTemplateService.java new file mode 100644 index 0000000..09c9073 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/template/db/QyTemplateService.java @@ -0,0 +1,15 @@ +package com.agileboot.domain.qywx.template.db; + +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 企业微信-代开发应用模板信息 服务类 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +public interface QyTemplateService extends IService { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/template/db/QyTemplateServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/template/db/QyTemplateServiceImpl.java new file mode 100644 index 0000000..9c416e0 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/template/db/QyTemplateServiceImpl.java @@ -0,0 +1,17 @@ +package com.agileboot.domain.qywx.template.db; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 企业微信-代开发应用模板信息 服务实现类 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +@Service +public class QyTemplateServiceImpl extends ServiceImpl implements QyTemplateService { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/db/QyUserEntity.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/db/QyUserEntity.java new file mode 100644 index 0000000..f0f2f71 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/db/QyUserEntity.java @@ -0,0 +1,151 @@ +package com.agileboot.domain.qywx.user.db; + +import com.agileboot.common.core.base.BaseEntity; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 企业微信-人员信息 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +@Getter +@Setter +@TableName("qy_user") +@ApiModel(value = "QyUserEntity对象", description = "企业微信-人员信息") +public class QyUserEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("主键编号") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @ApiModelProperty("操作序号") + @TableField("oper_id") + private Integer operId; + + @ApiModelProperty("全局唯一") + @TableField("open_userid") + private String openUserid; + + @ApiModelProperty("成员UserID。对应管理端的账号,企业内必须唯一。不区分大小写") + @TableField("userid") + private String userid; + + @ApiModelProperty("成员名称") + @TableField("`name`") + private String name; + + @ApiModelProperty("手机号码") + @TableField("mobile") + private String mobile; + + @ApiModelProperty("成员所属部门id列表") + @TableField("department") + private String department; + + @ApiModelProperty("部门内的排序值,默认为0") + @TableField("user_order") + private String userOrder; + + @ApiModelProperty("职务信息") + @TableField("position") + private String position; + + @ApiModelProperty("性别。0表示未定义,1表示男性,2表示女性。") + @TableField("gender") + private String gender; + + @ApiModelProperty("邮箱") + @TableField("email") + private String email; + + @ApiModelProperty("企业邮箱") + @TableField("biz_mail") + private String bizMail; + + @ApiModelProperty("表示在所在的部门内是否为部门负责人,数量与department一致") + @TableField("is_leader_in_dept") + private String isLeaderInDept; + + @ApiModelProperty("直属上级UserID") + @TableField("direct_leader") + private String directLeader; + + @TableField("avatar") + private String avatar; + + @ApiModelProperty("头像缩略图url") + @TableField("thumb_avatar") + private String thumbAvatar; + + @ApiModelProperty("座机") + @TableField("telephone") + private String telephone; + + @ApiModelProperty("别名") + @TableField("alias") + private String alias; + + @ApiModelProperty("扩展属性") + @TableField("extattr") + private String extattr; + + @ApiModelProperty("激活状态: 1=已激活,2=已禁用,4=未激活,5=退出企业。") + @TableField("`status`") + private String status; + + @ApiModelProperty("员工个人二维码") + @TableField("qr_code") + private String qrCode; + + @ApiModelProperty("成员对外属性") + @TableField("external_profile") + private String externalProfile; + + @ApiModelProperty("对外职务") + @TableField("external_position") + private String externalPosition; + + @ApiModelProperty("地址") + @TableField("address") + private String address; + + @ApiModelProperty("主部门,仅当应用对主部门有查看权限时返回。") + @TableField("main_department") + private String mainDepartment; + + @ApiModelProperty("有效标志(1-有效,0-无效)") + @TableField("`enable`") + private String enable; + + @ApiModelProperty("企业微信id") + @TableField("corpid") + private String corpid; + + @ApiModelProperty("应用ID") + @TableField("appid") + private String appid; + + @ApiModelProperty("公司ID") + @TableField("cid") + private Integer cid; + + + @Override + public Serializable pkVal() { + return this.id; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/db/QyUserMapper.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/db/QyUserMapper.java new file mode 100644 index 0000000..9d7feca --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/db/QyUserMapper.java @@ -0,0 +1,15 @@ +package com.agileboot.domain.qywx.user.db; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 企业微信-人员信息 Mapper 接口 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +public interface QyUserMapper extends BaseMapper { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/db/QyUserService.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/db/QyUserService.java new file mode 100644 index 0000000..47edc09 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/db/QyUserService.java @@ -0,0 +1,15 @@ +package com.agileboot.domain.qywx.user.db; + +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 企业微信-人员信息 服务类 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +public interface QyUserService extends IService { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/db/QyUserServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/db/QyUserServiceImpl.java new file mode 100644 index 0000000..94fc383 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/db/QyUserServiceImpl.java @@ -0,0 +1,17 @@ +package com.agileboot.domain.qywx.user.db; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 企业微信-人员信息 服务实现类 + *

+ * + * @author valarchie + * @since 2025-03-12 + */ +@Service +public class QyUserServiceImpl extends ServiceImpl implements QyUserService { + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java index c780371..9496d54 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java @@ -1,6 +1,7 @@ package com.agileboot.domain.shop.order; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DateUtil; import com.agileboot.common.core.page.PageDTO; import com.agileboot.common.exception.ApiException; import com.agileboot.common.exception.error.ErrorCode; @@ -25,9 +26,11 @@ import com.agileboot.domain.shop.payment.dto.WxJsApiPreCreateResponse; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.Date; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.time.DateUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -50,13 +53,19 @@ public class OrderApplicationService { @Transactional public CreateOrderResult createOrder(SubmitOrderCommand command) { - ShopOrderEntity order = command.getOrder(); List goodsList = command.getGoodsList(); OrderModel orderModel = orderModelFactory.create(); - BeanUtil.copyProperties(order, orderModel); + orderModel.setOpenid(command.getOpenid()); + orderModel.setCreatorId(1L); + orderModel.setCreateTime(new Date()); + orderModel.setUpdaterId(1L); + orderModel.setUpdateTime(new Date()); + orderModel.setDeleted(false); + orderModel.setStatus(1); orderModel.generateOrderNumber(); + orderModel.setTotalAmount(BigDecimal.valueOf(0)); orderModel.insert(); processOrderGoods(orderModel, goodsList); @@ -65,7 +74,7 @@ public class OrderApplicationService { WxJsApiPreCreateRequest paymentRequest = buildPaymentRequest(orderModel); WxJsApiPreCreateResponse paymentResponse = paymentApplicationService.callJsApiPreCreate(paymentRequest); - return new CreateOrderResult(orderModel.getOrderId(), paymentResponse); + return new CreateOrderResult(orderModel.getOrderId(), orderModel.getTotalAmount(), paymentResponse); } @@ -74,8 +83,12 @@ public class OrderApplicationService { request.setIp("222.218.10.217"); request.setOpenid(orderModel.getOpenid()); // request.setBiz_order_id(String.valueOf(orderModel.getOrderId())); // 使用订单唯一编号 - // 金额转换(元转分)建议增加精度处理 - request.setPay_amount(orderModel.getTotalAmount().toPlainString()); + // 金额转换(元转分)并四舍五入 + BigDecimal amountInFen = orderModel.getTotalAmount() + .multiply(new BigDecimal("100")) + .setScale(0, RoundingMode.HALF_UP); + request.setPay_amount(amountInFen.toPlainString()); // 单位转换为分 + request.setTitle("商品订单支付"); request.setNotify_url("http://wxshop.ab98.cn/shop-back-end/api/payment/callback"); request.setBiz_id("wxshop"); @@ -92,6 +105,11 @@ public class OrderApplicationService { // 设置订单ID关联 goodsModel.setOrderId(orderModel.getOrderId()); + goodsModel.setCreatorId(1L); + goodsModel.setCreateTime(new Date()); + goodsModel.setUpdaterId(1L); + goodsModel.setUpdateTime(new Date()); + goodsModel.setDeleted(false); // 计算商品金额并验证库存 goodsModel.calculateTotal(); diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/dto/CreateOrderResult.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/dto/CreateOrderResult.java index c2f37d3..bbb2bcb 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/dto/CreateOrderResult.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/dto/CreateOrderResult.java @@ -1,6 +1,7 @@ package com.agileboot.domain.shop.order.dto; import com.agileboot.domain.shop.payment.dto.WxJsApiPreCreateResponse; +import java.math.BigDecimal; import lombok.AllArgsConstructor; import lombok.Data; @@ -8,5 +9,6 @@ import lombok.Data; @AllArgsConstructor public class CreateOrderResult { private Long orderId; + private BigDecimal totalAmount; private WxJsApiPreCreateResponse paymentInfo; } \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/model/OrderGoodsModelFactory.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/model/OrderGoodsModelFactory.java index 04c4834..f6ab949 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/model/OrderGoodsModelFactory.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/model/OrderGoodsModelFactory.java @@ -3,18 +3,16 @@ package com.agileboot.domain.shop.order.model; import com.agileboot.domain.shop.goods.db.ShopGoodsService; import com.agileboot.domain.shop.order.db.ShopOrderGoodsEntity; import com.agileboot.domain.shop.order.db.ShopOrderGoodsService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +@Component +@RequiredArgsConstructor public class OrderGoodsModelFactory { private final ShopOrderGoodsService orderGoodsService; private final ShopGoodsService goodsService; - public OrderGoodsModelFactory(ShopOrderGoodsService orderGoodsService, - ShopGoodsService goodsService) { - this.orderGoodsService = orderGoodsService; - this.goodsService = goodsService; - } - public OrderGoodsModel create(ShopOrderGoodsEntity entity) { return new OrderGoodsModel(entity, orderGoodsService, goodsService); } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/model/OrderModel.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/model/OrderModel.java index 6246f15..bbfbf0f 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/model/OrderModel.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/model/OrderModel.java @@ -65,7 +65,7 @@ public class OrderModel extends ShopOrderEntity { } public void generateOrderNumber() { // 实现订单号生成逻辑(示例:时间戳+随机数) - String orderNo = "OD" + System.currentTimeMillis() + (int)(Math.random()*1000); + String orderNo = String.valueOf(System.currentTimeMillis() + (int)(Math.random()*1000)); this.setUcid(orderNo); } } \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/model/OrderModelFactory.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/model/OrderModelFactory.java index 4509602..97e66fa 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/model/OrderModelFactory.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/model/OrderModelFactory.java @@ -5,18 +5,16 @@ import com.agileboot.common.exception.error.ErrorCode; import com.agileboot.domain.shop.order.db.ShopOrderEntity; import com.agileboot.domain.shop.order.db.ShopOrderGoodsService; import com.agileboot.domain.shop.order.db.ShopOrderService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +@Component +@RequiredArgsConstructor public class OrderModelFactory { private final ShopOrderService orderService; private final ShopOrderGoodsService orderGoodsService; - public OrderModelFactory(ShopOrderService orderService, - ShopOrderGoodsService orderGoodsService) { - this.orderService = orderService; - this.orderGoodsService = orderGoodsService; - } - public OrderModel create(ShopOrderEntity entity) { return new OrderModel(entity, orderService, orderGoodsService); } diff --git a/agileboot-domain/src/main/resources/mapper/qy/QyAccessTokenMapper.xml b/agileboot-domain/src/main/resources/mapper/qy/QyAccessTokenMapper.xml new file mode 100644 index 0000000..79b81a3 --- /dev/null +++ b/agileboot-domain/src/main/resources/mapper/qy/QyAccessTokenMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/agileboot-domain/src/main/resources/mapper/qy/QyAuthCorpInfoMapper.xml b/agileboot-domain/src/main/resources/mapper/qy/QyAuthCorpInfoMapper.xml new file mode 100644 index 0000000..21c85a4 --- /dev/null +++ b/agileboot-domain/src/main/resources/mapper/qy/QyAuthCorpInfoMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/agileboot-domain/src/main/resources/mapper/qy/QyAuthMapper.xml b/agileboot-domain/src/main/resources/mapper/qy/QyAuthMapper.xml new file mode 100644 index 0000000..af5b6aa --- /dev/null +++ b/agileboot-domain/src/main/resources/mapper/qy/QyAuthMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/agileboot-domain/src/main/resources/mapper/qy/QyDepartmentMapper.xml b/agileboot-domain/src/main/resources/mapper/qy/QyDepartmentMapper.xml new file mode 100644 index 0000000..299fde4 --- /dev/null +++ b/agileboot-domain/src/main/resources/mapper/qy/QyDepartmentMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/agileboot-domain/src/main/resources/mapper/qy/QyMessageMapper.xml b/agileboot-domain/src/main/resources/mapper/qy/QyMessageMapper.xml new file mode 100644 index 0000000..f34087c --- /dev/null +++ b/agileboot-domain/src/main/resources/mapper/qy/QyMessageMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/agileboot-domain/src/main/resources/mapper/qy/QyTemplateMapper.xml b/agileboot-domain/src/main/resources/mapper/qy/QyTemplateMapper.xml new file mode 100644 index 0000000..288855c --- /dev/null +++ b/agileboot-domain/src/main/resources/mapper/qy/QyTemplateMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/agileboot-domain/src/main/resources/mapper/qy/QyUserMapper.xml b/agileboot-domain/src/main/resources/mapper/qy/QyUserMapper.xml new file mode 100644 index 0000000..b98c0c9 --- /dev/null +++ b/agileboot-domain/src/main/resources/mapper/qy/QyUserMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CodeGenerator.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CodeGenerator.java index 5da0de6..d97fe95 100644 --- a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CodeGenerator.java +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CodeGenerator.java @@ -61,7 +61,7 @@ public class CodeGenerator { //生成的类 放在orm子模块下的/target/generated-code目录底下 .module("/agileboot-orm/target/generated-code") .parentPackage("com.agileboot") - .tableName("shop_order_goods") + .tableName("qy_user") // 决定是否继承基类 .isExtendsFromBaseEntity(true) .build(); diff --git a/build.bat b/build.bat index 672431f..5791d84 100644 --- a/build.bat +++ b/build.bat @@ -1,5 +1,2 @@ mvn clean package -pl agileboot-api -am -mvn clean package -pl agileboot-admin -am - -mvn clean package assembly:single -pl agileboot-api -mvn clean package assembly:single -pl agileboot-admin \ No newline at end of file +mvn clean package -pl agileboot-admin -am \ No newline at end of file diff --git a/sql/20250308.sql b/sql/20250308.sql index 139fcda..e269d77 100644 --- a/sql/20250308.sql +++ b/sql/20250308.sql @@ -2,7 +2,7 @@ CREATE TABLE `shop_order` ( `order_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '订单唯一ID', `ucid` VARCHAR(32) DEFAULT NULL COMMENT 'ucid', `openid` VARCHAR(32) NOT NULL COMMENT 'openid', - `trade_id` VARCHAR(32) NOT NULL COMMENT '支付网关交易id', + `trade_id` VARCHAR(32) NULL COMMENT '支付网关交易id', `total_amount` DECIMAL(15,2) NOT NULL COMMENT '订单总金额', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '订单状态(1待付款 2已付款 3已发货 4已完成 5已取消)', `pay_status` TINYINT NOT NULL DEFAULT 1 COMMENT '支付状态(1未支付 2已支付 3退款中 4已退款)', @@ -25,7 +25,7 @@ CREATE TABLE `shop_order_goods` ( `goods_id` BIGINT NOT NULL COMMENT '关联商品ID', `quantity` INT NOT NULL DEFAULT 1 COMMENT '购买数量', `price` DECIMAL(15,2) NOT NULL COMMENT '购买时单价', - `total_amount` DECIMAL(15,2) GENERATED ALWAYS AS (quantity * price) STORED COMMENT '商品总金额', + `total_amount` DECIMAL(15,2) NOT NULL COMMENT '商品总金额', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '商品状态(1正常 2已退货 3已换货)', `creator_id` BIGINT NOT NULL DEFAULT 0 COMMENT '创建者ID', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',