diff --git a/exam-admin/pom.xml b/exam-admin/pom.xml index b27b644..0b62a93 100644 --- a/exam-admin/pom.xml +++ b/exam-admin/pom.xml @@ -179,6 +179,12 @@ ${wechatpay-apiv3.version} + + + org.springframework.boot + spring-boot-starter-data-redis + + org.apache.httpcomponents diff --git a/exam-admin/src/main/java/com/yf/exam/ability/Constant.java b/exam-admin/src/main/java/com/yf/exam/ability/Constant.java deleted file mode 100644 index 9880ea6..0000000 --- a/exam-admin/src/main/java/com/yf/exam/ability/Constant.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.yf.exam.ability; - - -/** - * 通用常量 - * @author bool - */ -public class Constant { - - - /** - * 文件上传路径 - */ - public static final String FILE_PREFIX = "/upload/file/"; -} diff --git a/exam-admin/src/main/java/com/yf/exam/ability/shiro/aop/JwtFilter.java b/exam-admin/src/main/java/com/yf/exam/ability/shiro/aop/JwtFilter.java index 88cf448..6af1d55 100644 --- a/exam-admin/src/main/java/com/yf/exam/ability/shiro/aop/JwtFilter.java +++ b/exam-admin/src/main/java/com/yf/exam/ability/shiro/aop/JwtFilter.java @@ -1,16 +1,14 @@ package com.yf.exam.ability.shiro.aop; - import com.yf.exam.ability.shiro.jwt.JwtToken; import com.yf.exam.aspect.utils.InjectUtils; -import com.yf.exam.modules.Constant; -import lombok.extern.slf4j.Slf4j; -import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; - +import com.yf.exam.constant.Constants; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; /** * 鉴权登录拦截器 @@ -42,7 +40,7 @@ public class JwtFilter extends BasicHttpAuthenticationFilter { @Override protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest) request; - String token = httpServletRequest.getHeader(Constant.TOKEN); + String token = httpServletRequest.getHeader(Constants.TOKEN); JwtToken jwtToken = new JwtToken(token); // 提交给realm进行登入,如果错误他会抛出异常并被捕获 diff --git a/exam-admin/src/main/java/com/yf/exam/ability/upload/controller/UploadController.java b/exam-admin/src/main/java/com/yf/exam/ability/upload/controller/UploadController.java index d8b7035..7ec989f 100644 --- a/exam-admin/src/main/java/com/yf/exam/ability/upload/controller/UploadController.java +++ b/exam-admin/src/main/java/com/yf/exam/ability/upload/controller/UploadController.java @@ -1,14 +1,15 @@ package com.yf.exam.ability.upload.controller; - -import com.yf.exam.ability.Constant; import com.yf.exam.ability.upload.dto.UploadReqDTO; import com.yf.exam.ability.upload.dto.UploadRespDTO; import com.yf.exam.ability.upload.service.UploadService; +import com.yf.exam.constant.Constants; import com.yf.exam.core.api.ApiRest; import com.yf.exam.core.api.controller.BaseController; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -16,9 +17,6 @@ import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - /** * 本地文件上传下载请求类 * @author bool @@ -49,7 +47,7 @@ public class UploadController extends BaseController { * @param request * @param response */ - @GetMapping(Constant.FILE_PREFIX+"**") + @GetMapping(Constants.FILE_PREFIX+"**") @ApiOperation(value = "文件下载", notes = "文件下载") public void download(HttpServletRequest request, HttpServletResponse response) { uploadService.download(request, response); diff --git a/exam-admin/src/main/java/com/yf/exam/ability/upload/service/impl/UploadServiceImpl.java b/exam-admin/src/main/java/com/yf/exam/ability/upload/service/impl/UploadServiceImpl.java index 802e8ab..f8cec70 100644 --- a/exam-admin/src/main/java/com/yf/exam/ability/upload/service/impl/UploadServiceImpl.java +++ b/exam-admin/src/main/java/com/yf/exam/ability/upload/service/impl/UploadServiceImpl.java @@ -1,6 +1,5 @@ package com.yf.exam.ability.upload.service.impl; -import com.yf.exam.ability.Constant; import com.yf.exam.ability.upload.config.UploadConfig; import com.yf.exam.ability.upload.config.UploadPathConfig; import com.yf.exam.ability.upload.dto.UploadReqDTO; @@ -155,7 +154,7 @@ public class UploadServiceImpl implements UploadService { */ public String getRealPath(String uri){ - String regx = Constant.FILE_PREFIX+"(.*)"; + String regx = Constants.FILE_PREFIX+"(.*)"; // 查找全部变量 Pattern pattern = Pattern.compile(regx); diff --git a/exam-admin/src/main/java/com/yf/exam/config/AppletChatConfig.java b/exam-admin/src/main/java/com/yf/exam/config/AppletChatConfig.java new file mode 100644 index 0000000..1631b74 --- /dev/null +++ b/exam-admin/src/main/java/com/yf/exam/config/AppletChatConfig.java @@ -0,0 +1,48 @@ +package com.yf.exam.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @Description 微信小程序参数配置类 + * @Author 纪寒 + * @Date 2022-09-02 10:52:38 + * @Version 1.0 + */ +@Data +@Component +@ConfigurationProperties(prefix = "applet-chat-config") +public class AppletChatConfig { + + /** + * 小程序id + */ + private String appletId; + + /** + * 小程序secret + */ + private String secret; + + /** + * 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语 + */ + private String lang; + + /** + * 授权类型 + */ + private String grantType; + + /** + * 微信小程序事件回调令牌 + */ + private String token; + + /** + * 微信小程序事件回调消息加密密钥 + */ + private String encodingAesKey; + +} diff --git a/exam-admin/src/main/java/com/yf/exam/config/ShiroConfig.java b/exam-admin/src/main/java/com/yf/exam/config/ShiroConfig.java index 0f0e63a..09e081a 100644 --- a/exam-admin/src/main/java/com/yf/exam/config/ShiroConfig.java +++ b/exam-admin/src/main/java/com/yf/exam/config/ShiroConfig.java @@ -50,6 +50,9 @@ public class ShiroConfig { map.put("/exam/api/sys/user/quick-reg", "anon"); map.put("/exam/api/sys/user/info", "anon"); + // 小程序接口 + map.put("/examApplet/**", "anon"); + // 获取网站基本信息 map.put("/exam/api/sys/config/detail", "anon"); diff --git a/exam-admin/src/main/java/com/yf/exam/config/SwaggerConfig.java b/exam-admin/src/main/java/com/yf/exam/config/SwaggerConfig.java index d4208aa..0c844f3 100644 --- a/exam-admin/src/main/java/com/yf/exam/config/SwaggerConfig.java +++ b/exam-admin/src/main/java/com/yf/exam/config/SwaggerConfig.java @@ -2,6 +2,7 @@ package com.yf.exam.config; import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI; import io.swagger.annotations.ApiOperation; +import java.util.Collections; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -16,8 +17,6 @@ import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; -import java.util.Collections; - /** * Swagger配置 * @author bool @@ -37,7 +36,8 @@ public class SwaggerConfig { .groupName("考试模块接口") .select() .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) - .paths(PathSelectors.ant("/exam/api/**")) + //.paths(PathSelectors.ant("/exam/api/**")) + .paths(PathSelectors.any()) .build() .securitySchemes(Collections.singletonList(securityScheme())); } diff --git a/exam-admin/src/main/java/com/yf/exam/config/XinYiLuConfig.java b/exam-admin/src/main/java/com/yf/exam/config/XinYiLuConfig.java new file mode 100644 index 0000000..fa66031 --- /dev/null +++ b/exam-admin/src/main/java/com/yf/exam/config/XinYiLuConfig.java @@ -0,0 +1,578 @@ +package com.yf.exam.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 读取项目相关配置 + * + * @author xinyilu + */ +@Component +@ConfigurationProperties(prefix = "xinyilu") +public class XinYiLuConfig { + /** + * 项目名称 + */ + private String name; + + /** + * 版本 + */ + private String version; + + /** + * 版权年份 + */ + private String copyrightYear; + + /** + * 实例演示开关 + */ + private boolean demoEnabled; + + /** + * 上传路径 + */ + private static String profile; + + /** + * 获取地址开关 + */ + private static boolean addressEnabled; + + /** + * 验证码类型 + */ + private static String captchaType; + + /** + * 柏杏家医云小程序图片存放路径 + */ + private String nurserAppletPicture; + + /** + * 护理站图片上传存放路径 + */ + private String nurserStationPictureUrl; + + /** + * 护理站图片上传存放路径 + */ + private String nurserStationIntroducePictureUrl; + + /** + * 护理站项目图片上传存放路径 + */ + private String nurserStationItemPictureUrl; + + /** + * 商品图片路径上传存放路径 + */ + private String goodsPictureUrl; + + /** + * 商品属性图片地址 + */ + private String attributePitureUrl; + + /** + * 修改会员App用户头像上传 + */ + private String headPictureUrl; + + /** + * 评价信息图片上传路径 + */ + private String evaluatePictureUrl; + + /** + * 任务完成上传图片 + */ + private String appointmentOrderDetailsUrl; + + /** + * 护理机构上传图片 + */ + private String nurseStationClassifyUrl; + + /** + * 海报图片存放路径 + */ + private String posterPictureUrl; + + /** + * 海报视频存放路径 + */ + private String posterVideoUrl; + + /** + * 资讯主缩略图地址 + */ + private String leadThumbnailUrl; + + /** + * 商品分类图片地址 + */ + private String goodsCategoryPicture; + + /** + * 护理员App人员头像的上传路径图片上传路径 + */ + private String nurseStationAppPersonUrl; + + /** + * 获取管理端富文本的上传路径 + */ + private String richTextPictureUrl; + + /** + * 护理项目分类图标存放地址存放路径 + */ + private String nurseItemClassifyUrl; + + /** + * 在线客服群二维码存放路径 + */ + private String groupQrCodeUrl; + + /** + * 科室人员证书图片存放路径 + */ + private String certificateUrl; + + /** + * 健康咨询 科室人员头像地址 + */ + private String personPictureUrl; + + /** + * 护理人员证书地址 + */ + private String personCertificateUrl; + + /** + * 审核护理人员证书地址 + */ + private String personCertificateCheckUrl; + + /** + * 医路 + */ + private String yiLuYouPinStoreName; + + /** + * 护理员App上传路径 + */ + private String appFileName; + + /** + * 泉医到家App文件存放路径 + */ + private String appFilePath; + + /** + * 培训分类图片路径 + */ + private String trainingCategoryPictureUrl; + + /** + * 培训项目logo图片路径 + */ + private String trainingItemCoverUrl; + + /** + * 培训项目海报图片路径 + */ + private String trainingItemPosterUrl; + + /** + * 培训项目海报图片路径 + */ + private String trainingItemContentUrl; + + /** + * 培训项目章节视频存放地址 + */ + private String itemDirectoryUrl; + + /** + * 微信好有邀请二维码存放路径 + */ + private String personalWeChatCodeUrl; + + /** + * 护理站二维码存放路径 + */ + private String stationWechatCodeUrl; + + /** + * 实人认证身份证照片地址 + */ + private String cardNoPicUrl; + + /** + * 预约订单护理项目告知书签字图片地址 + */ + private String itemInformUrl; + + public String getItemInformUrl() { + return itemInformUrl; + } + + public void setItemInformUrl(String itemInformUrl) { + this.itemInformUrl = itemInformUrl; + } + + public String getCardNoPicUrl() { + return cardNoPicUrl; + } + + public void setCardNoPicUrl(String cardNoPicUrl) { + this.cardNoPicUrl = cardNoPicUrl; + } + + public String getStationWechatCodeUrl() { + return stationWechatCodeUrl; + } + + public void setStationWechatCodeUrl(String stationWechatCodeUrl) { + this.stationWechatCodeUrl = stationWechatCodeUrl; + } + + public String getPersonalWeChatCodeUrl() { + return personalWeChatCodeUrl; + } + + public void setPersonalWeChatCodeUrl(String personalWeChatCodeUrl) { + this.personalWeChatCodeUrl = personalWeChatCodeUrl; + } + + public String getAppFilePath() { + return appFilePath; + } + + public void setAppFilePath(String appFilePath) { + this.appFilePath = appFilePath; + } + + public String getYiLuYouPinStoreName() { + return yiLuYouPinStoreName; + } + + public void setYiLuYouPinStoreName(String yiLuYouPinStoreName) { + this.yiLuYouPinStoreName = yiLuYouPinStoreName; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getCopyrightYear() { + return copyrightYear; + } + + public void setCopyrightYear(String copyrightYear) { + this.copyrightYear = copyrightYear; + } + + public boolean isDemoEnabled() { + return demoEnabled; + } + + public void setDemoEnabled(boolean demoEnabled) { + this.demoEnabled = demoEnabled; + } + + public static String getProfile() { + return profile; + } + + public void setProfile(String profile) { + XinYiLuConfig.profile = profile; + } + + public static boolean isAddressEnabled() { + return addressEnabled; + } + + public void setAddressEnabled(boolean addressEnabled) { + XinYiLuConfig.addressEnabled = addressEnabled; + } + + public static String getCaptchaType() { + return captchaType; + } + + public void setCaptchaType(String captchaType) { + XinYiLuConfig.captchaType = captchaType; + } + + /** + * 获取导入上传路径 + */ + public static String getImportPath() { + return getProfile() + "/import"; + } + + /** + * 获取头像上传路径 + */ + public static String getAvatarPath() { + return getProfile() + "/avatar"; + } + + /** + * 获取下载路径 + */ + public static String getDownloadPath() { + return getProfile() + "/download/"; + } + + /** + * 获取上传路径 + */ + public static String getUploadPath() { + return getProfile() + "/upload"; + } + + public String getNurserAppletPicture() { + return nurserAppletPicture; + } + + public void setNurserAppletPicture(String nurserAppletPicture) { + this.nurserAppletPicture = nurserAppletPicture; + } + + public String getNurserStationPictureUrl() { + return nurserStationPictureUrl; + } + + public void setNurserStationPictureUrl(String nurserStationPictureUrl) { + this.nurserStationPictureUrl = nurserStationPictureUrl; + } + + public String getNurserStationIntroducePictureUrl() { + return nurserStationIntroducePictureUrl; + } + + public void setNurserStationIntroducePictureUrl(String nurserStationIntroducePictureUrl) { + this.nurserStationIntroducePictureUrl = nurserStationIntroducePictureUrl; + } + + public String getNurserStationItemPictureUrl() { + return nurserStationItemPictureUrl; + } + + public void setNurserStationItemPictureUrl(String nurserStationItemPictureUrl) { + this.nurserStationItemPictureUrl = nurserStationItemPictureUrl; + } + + public String getGoodsPictureUrl() { + return goodsPictureUrl; + } + + public void setGoodsPictureUrl(String goodsPictureUrl) { + this.goodsPictureUrl = goodsPictureUrl; + } + + public String getHeadPictureUrl() { + return headPictureUrl; + } + + public void setHeadPictureUrl(String headPictureUrl) { + this.headPictureUrl = headPictureUrl; + } + + public String getAppointmentOrderDetailsUrl() { + return appointmentOrderDetailsUrl; + } + + public void setAppointmentOrderDetailsUrl(String appointmentOrderDetailsUrl) { + this.appointmentOrderDetailsUrl = appointmentOrderDetailsUrl; + } + + public String getAttributePitureUrl() { + return attributePitureUrl; + } + + public void setAttributePitureUrl(String attributePitureUrl) { + this.attributePitureUrl = attributePitureUrl; + } + + + public String getAppFileName() { + return appFileName; + } + + public void setAppFileName(String appFileName) { + this.appFileName = appFileName; + } + + public String getNurseStationAppPersonUrl() { + return nurseStationAppPersonUrl; + } + + public void setNurseStationAppPersonUrl(String nurseStationAppPersonUrl) { + this.nurseStationAppPersonUrl = nurseStationAppPersonUrl; + } + + public String getEvaluatePictureUrl() { + return evaluatePictureUrl; + } + + public void setEvaluatePictureUrl(String evaluatePictureUrl) { + this.evaluatePictureUrl = evaluatePictureUrl; + } + + public String getRichTextPictureUrl() { + return richTextPictureUrl; + } + + public void setRichTextPictureUrl(String richTextPictureUrl) { + this.richTextPictureUrl = richTextPictureUrl; + } + + public String getGoodsCategoryPicture() { + return goodsCategoryPicture; + } + + public void setGoodsCategoryPicture(String goodsCategoryPicture) { + this.goodsCategoryPicture = goodsCategoryPicture; + } + + public String getNurseStationClassifyUrl() { + return nurseStationClassifyUrl; + } + + public void setNurseStationClassifyUrl(String nurseStationClassifyUrl) { + this.nurseStationClassifyUrl = nurseStationClassifyUrl; + } + + public String getNurseItemClassifyUrl() { + return nurseItemClassifyUrl; + } + + public void setNurseItemClassifyUrl(String nurseItemClassifyUrl) { + this.nurseItemClassifyUrl = nurseItemClassifyUrl; + } + + public String getPosterPictureUrl() { + return posterPictureUrl; + } + + public void setPosterPictureUrl(String posterPictureUrl) { + this.posterPictureUrl = posterPictureUrl; + } + + public String getLeadThumbnailUrl() { + return leadThumbnailUrl; + } + + public void setLeadThumbnailUrl(String leadThumbnailUrl) { + this.leadThumbnailUrl = leadThumbnailUrl; + } + + + public String getPosterVideoUrl() { + return posterVideoUrl; + } + + public void setPosterVideoUrl(String posterVideoUrl) { + this.posterVideoUrl = posterVideoUrl; + } + + + public String getGroupQrCodeUrl() { + return groupQrCodeUrl; + } + + public void setGroupQrCodeUrl(String groupQrCodeUrl) { + this.groupQrCodeUrl = groupQrCodeUrl; + } + + + public String getCertificateUrl() { + return certificateUrl; + } + + public void setCertificateUrl(String certificateUrl) { + this.certificateUrl = certificateUrl; + } + + public String getPersonPictureUrl() { + return personPictureUrl; + } + + public void setPersonPictureUrl(String personPictureUrl) { + this.personPictureUrl = personPictureUrl; + } + + public String getPersonCertificateUrl() { + return personCertificateUrl; + } + + public void setPersonCertificateUrl(String personCertificateUrl) { + this.personCertificateUrl = personCertificateUrl; + } + + public String getPersonCertificateCheckUrl() { + return personCertificateCheckUrl; + } + + public void setPersonCertificateCheckUrl(String personCertificateCheckUrl) { + this.personCertificateCheckUrl = personCertificateCheckUrl; + } + + public String getTrainingCategoryPictureUrl() { + return trainingCategoryPictureUrl; + } + + public void setTrainingCategoryPictureUrl(String trainingCategoryPictureUrl) { + this.trainingCategoryPictureUrl = trainingCategoryPictureUrl; + } + + public String getTrainingItemCoverUrl() { + return trainingItemCoverUrl; + } + + public void setTrainingItemCoverUrl(String trainingItemCoverUrl) { + this.trainingItemCoverUrl = trainingItemCoverUrl; + } + + public String getTrainingItemPosterUrl() { + return trainingItemPosterUrl; + } + + public void setTrainingItemPosterUrl(String trainingItemPosterUrl) { + this.trainingItemPosterUrl = trainingItemPosterUrl; + } + + public String getTrainingItemContentUrl() { + return trainingItemContentUrl; + } + + public void setTrainingItemContentUrl(String trainingItemContentUrl) { + this.trainingItemContentUrl = trainingItemContentUrl; + } + + public String getItemDirectoryUrl() { + return itemDirectoryUrl; + } + + public void setItemDirectoryUrl(String itemDirectoryUrl) { + this.itemDirectoryUrl = itemDirectoryUrl; + } +} diff --git a/exam-admin/src/main/java/com/yf/exam/config/XylWeChatPaymentConfig.java b/exam-admin/src/main/java/com/yf/exam/config/XylWeChatPaymentConfig.java index 60f69b0..a66eedb 100644 --- a/exam-admin/src/main/java/com/yf/exam/config/XylWeChatPaymentConfig.java +++ b/exam-admin/src/main/java/com/yf/exam/config/XylWeChatPaymentConfig.java @@ -5,7 +5,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** - * @Description 新医路商户号微信支付参数配置类 + * @Description 山东省公共卫生学会商户号微信支付参数配置类 * @Author 纪寒 * @Date 2022-10-17 16:55:04 * @Version 1.0 @@ -16,27 +16,27 @@ import org.springframework.stereotype.Component; public class XylWeChatPaymentConfig { /** - * 新医路商户号 + * 山东省公共卫生学会商户号 */ private String xylMchId; /** - * 新医路API证书序号getXylVerifier + * 山东省公共卫生学会证书序号getXylVerifier */ private String xylMchSerialNo; /** - * 新医路私钥文件 + * 山东省公共卫生学会私钥文件 */ private String xylPrivateKeyPath; /** - * 新医路API V3版本密钥 + * 山东省公共卫生学会API V3版本密钥 */ private String xylPaymentKey; /** - * 新医路支付回调地址 + * 山东省公共卫生学会支付回调地址 */ private String xylWeChatNotifyUrl; } diff --git a/exam-admin/src/main/java/com/yf/exam/config/payment/XylWeChatPaymentUtilConfig.java b/exam-admin/src/main/java/com/yf/exam/config/payment/XylWeChatPaymentUtilConfig.java index 06a5f0f..a378546 100644 --- a/exam-admin/src/main/java/com/yf/exam/config/payment/XylWeChatPaymentUtilConfig.java +++ b/exam-admin/src/main/java/com/yf/exam/config/payment/XylWeChatPaymentUtilConfig.java @@ -19,7 +19,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; /** - * @Description 新医路商户号微信支付核心配置类 + * @Description 山东省公共卫生学会商户号微信支付核心配置类 * @Author 纪寒 * @Date 2022-10-17 18:56:22 * @Version 1.0 @@ -32,7 +32,7 @@ public class XylWeChatPaymentUtilConfig { private XylWeChatPaymentConfig xylWeChatPaymentConfig; /** - * 获取新医路签名信息 + * 获取山东省公共卫生学会签名信息 * * @return 签名信息 * @throws Exception 异常信息 @@ -54,36 +54,36 @@ public class XylWeChatPaymentUtilConfig { } /** - * 获取新医路商户号带有签名的http请求对象 + * 获取山东省公共卫生学会商户号带有签名的http请求对象 * * @return CloseableHttpClient对象 */ @Bean(name = "xinYiLuWeChatPayClient") public CloseableHttpClient getXinYiLuWeChatPayClient(Verifier xylVerifier) { - log.info("开始获取新医路商户带有签名信息的http请求对象........"); + log.info("开始获取山东省公共卫生学会商户带有签名信息的http请求对象........"); //获取商户私钥 PrivateKey privateKey = getPrivateKey(xylWeChatPaymentConfig.getXylPrivateKeyPath()); CloseableHttpClient closeableHttpClient = null; try { - log.info("新医路签名验证证书,xinYiLuCertificatesVerifier:{}", xylVerifier); + log.info("山东省公共卫生学会签名验证证书,xinYiLuCertificatesVerifier:{}", xylVerifier); //通过WeChatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新 closeableHttpClient = WechatPayHttpClientBuilder.create() .withMerchant(xylWeChatPaymentConfig.getXylMchId(), xylWeChatPaymentConfig.getXylMchSerialNo(), privateKey) .withValidator(new WechatPay2Validator(xylVerifier)).build(); } catch (Exception e) { - log.error("新医路验证签名信息失败,失败信息:{}", e.getMessage()); + log.error("山东省公共卫生学会验证签名信息失败,失败信息:{}", e.getMessage()); } return closeableHttpClient; } /** - * 获取新医路商户号无签名的http请求对象 + * 获取山东省公共卫生学会商户号无签名的http请求对象 * * @return CloseableHttpClient对象 */ @Bean(name = "xinYiLuWeChatPayNoSignClient") public CloseableHttpClient getXinYiLuWeChatPayNoSignClient() { - log.info("获取新医路商户的无签名信息的http请求对象........."); + log.info("获取山东省公共卫生学会商户的无签名信息的http请求对象........."); //获取商户私钥 PrivateKey privateKey = getPrivateKey(xylWeChatPaymentConfig.getXylPrivateKeyPath()); //通过WeChatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新 @@ -102,8 +102,8 @@ public class XylWeChatPaymentUtilConfig { try { return PemUtil.loadPrivateKey(new ClassPathResource(filename).getInputStream()); } catch (Exception e) { - log.error("新医路商户私钥文件不存在,错误原因:{}", e.getMessage()); - throw new ServiceException("新医路商户私钥文件不存在,请联系管理员!"); + log.error("山东省公共卫生学会商户私钥文件不存在,错误原因:{}", e.getMessage()); + throw new ServiceException("山东省公共卫生学会商户私钥文件不存在,请联系管理员!"); } } } diff --git a/exam-admin/src/main/java/com/yf/exam/constant/Constants.java b/exam-admin/src/main/java/com/yf/exam/constant/Constants.java index c91a1ea..b450366 100644 --- a/exam-admin/src/main/java/com/yf/exam/constant/Constants.java +++ b/exam-admin/src/main/java/com/yf/exam/constant/Constants.java @@ -7,6 +7,21 @@ package com.yf.exam.constant; */ public class Constants { + /** + * UTF-8 字符集 + */ + public static final String UTF8 = "UTF-8"; + + /** + * 会话 + */ + public static final String TOKEN = "token"; + + /** + * 文件上传路径 + */ + public static final String FILE_PREFIX = "/upload/file/"; + /** * 身份证正面照片路径上传 */ @@ -42,6 +57,11 @@ public class Constants { */ public static final String sign_picture_type = "signPictureUrl"; + /** + * 微信小程序ACCESS_TOKEN前缀 + */ + public static final String EXAM_APPLET_ACCESS_TOKEN = "EXAM_APPLET_ACCESS_TOKEN:"; + } diff --git a/exam-admin/src/main/java/com/yf/exam/core/domain/AjaxResult.java b/exam-admin/src/main/java/com/yf/exam/core/domain/AjaxResult.java new file mode 100644 index 0000000..f90bd9c --- /dev/null +++ b/exam-admin/src/main/java/com/yf/exam/core/domain/AjaxResult.java @@ -0,0 +1,165 @@ +package com.yf.exam.core.domain; + +import java.util.HashMap; + +/** + * 操作消息提醒 + * + * @author xinyilu + */ +public class AjaxResult extends HashMap { + private static final long serialVersionUID = 1L; + + /** + * 状态码 + */ + public static final String CODE_TAG = "code"; + + /** + * 返回内容 + */ + public static final String MSG_TAG = "msg"; + + /** + * 数据对象 + */ + public static final String DATA_TAG = "data"; + + /** + * 成功默认消息 + */ + private static final Integer CODE_SUCCESS = 0; + private static final String MSG_SUCCESS = "操作成功!"; + + /** + * 失败默认消息 + */ + private static final Integer CODE_FAILURE = 1; + private static final String MSG_FAILURE = "请求失败!"; + + /** + * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 + */ + public AjaxResult() { + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + */ + public AjaxResult(int code, String msg) { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + * @param data 数据对象 + */ + public AjaxResult(int code, String msg, Object data) { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + if (!(data == null)) { + super.put(DATA_TAG, data); + } + } + + /** + * 返回成功消息 + * + * @return 成功消息 + */ + public static AjaxResult success() { + return AjaxResult.success("操作成功"); + } + + /** + * 返回成功数据 + * + * @return 成功消息 + */ + public static AjaxResult success(Object data) { + return AjaxResult.success("操作成功", data); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @return 成功消息 + */ + public static AjaxResult success(String msg) { + return AjaxResult.success(msg, null); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 成功消息 + */ + public static AjaxResult success(String msg, Object data) { + return new AjaxResult(CODE_SUCCESS, msg, data); + } + + /** + * 返回错误消息 + * + * @return + */ + public static AjaxResult error() { + return AjaxResult.error("操作失败"); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @return 警告消息 + */ + public static AjaxResult error(String msg) { + return AjaxResult.error(msg, null); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 警告消息 + */ + public static AjaxResult error(String msg, Object data) { + return new AjaxResult(CODE_FAILURE, msg, data); + } + + /** + * 返回错误消息 + * + * @param code 状态码 + * @param msg 返回内容 + * @return 警告消息 + */ + public static AjaxResult error(int code, String msg) { + return new AjaxResult(code, msg, null); + } + + /** + * 方便链式调用 + * + * @param key 键 + * @param value 值 + * @return 数据对象 + */ + @Override + public AjaxResult put(String key, Object value) { + super.put(key, value); + return this; + } + +} diff --git a/exam-admin/src/main/java/com/yf/exam/core/redis/RedisCache.java b/exam-admin/src/main/java/com/yf/exam/core/redis/RedisCache.java new file mode 100644 index 0000000..3b0346d --- /dev/null +++ b/exam-admin/src/main/java/com/yf/exam/core/redis/RedisCache.java @@ -0,0 +1,226 @@ +package com.yf.exam.core.redis; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import javax.annotation.Resource; +import org.springframework.data.redis.core.BoundSetOperations; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +/** + * spring redis 工具类 + * + * @author xinyilu + **/ +@SuppressWarnings(value = {"unchecked", "rawtypes"}) +@Component +public class RedisCache { + @Resource + public RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String key, final T value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public T getCacheObject(final String key) { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public long deleteObject(final Collection collection) { + return redisTemplate.delete(collection); + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public long setCacheList(final String key, final List dataList) { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public List getCacheList(final String key) { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet(final String key, final Set dataSet) { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public Set getCacheSet(final String key) { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public void setCacheMap(final String key, final Map dataMap) { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public Map getCacheMap(final String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public void setCacheMapValue(final String key, final String hKey, final T value) { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public T getCacheMapValue(final String key, final String hKey) { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 删除Hash中的数据 + * + * @param key + * @param hKey + */ + public void delCacheMapValue(final String key, final String hKey) { + HashOperations hashOperations = redisTemplate.opsForHash(); + hashOperations.delete(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public List getMultiCacheMapValue(final String key, final Collection hKeys) { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) { + return redisTemplate.keys(pattern); + } +} diff --git a/exam-admin/src/main/java/com/yf/exam/core/utils/AppletChatUtil.java b/exam-admin/src/main/java/com/yf/exam/core/utils/AppletChatUtil.java new file mode 100644 index 0000000..57271f0 --- /dev/null +++ b/exam-admin/src/main/java/com/yf/exam/core/utils/AppletChatUtil.java @@ -0,0 +1,277 @@ +package com.yf.exam.core.utils; + +import com.alibaba.fastjson2.JSON; +import com.yf.exam.config.AppletChatConfig; +import com.yf.exam.config.XinYiLuConfig; +import com.yf.exam.constant.Constants; +import com.yf.exam.core.exception.ServiceException; +import com.yf.exam.core.utils.http.HttpUtils; +import com.yf.exam.modules.applet.entity.AppletAccessToken; +import com.yf.exam.modules.applet.vo.AppletLoginVO; +import com.yf.exam.modules.applet.vo.AppletPhoneVO; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import javax.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +/** + * @Description 微信小程序工具类 + * @Author 纪寒 + * @Date 2022-08-17 16:11:10 + * @Version 1.0 + */ +@Slf4j +@Component +public class AppletChatUtil { + + private static RedisTemplate redisTemplate; + + private static AppletChatConfig appletChatConfig; + + @Resource + public void redisTemplate(RedisTemplate redisTemplate) { + AppletChatUtil.redisTemplate = redisTemplate; + } + + @Resource + public void appletChatConfig(AppletChatConfig appletChatConfig) { + AppletChatUtil.appletChatConfig = appletChatConfig; + } + + + /** + * 返回成功状态码 + */ + private static final int SUCCESS_CODE = 0; + + /** + * 小程序登录url + */ + private static final String APPLET_LOGIN_URL = "https://api.weixin.qq.com/sns/jscode2session"; + + /** + * 小程序accessToken的url + */ + private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"; + + /** + * 小程序获取用户手机号的url + */ + private static final String PHONE_NUMBER_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token="; + + /** + * 获取小程序二维码接口地址 + */ + private static final String GET_APPLET_CODE_URL = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token="; + + /** + * JSON数据格式标识 + */ + private static final String JSON_DATA_FORMAT = "Content-Type: application/json; encoding=utf-8"; + + /** + * 生成小程序二维码图片失败标识 + */ + private static final String FAIL = "FAIL"; + + /** + * 根据登录编码获取微信用户信息 + * + * @param appletId 小程序id + * @param secret 小程序秘钥 + * @param code 登录凭证码 + * @param grantType 授权类型 + * @return 登录信息 + */ + public static AppletLoginVO getAppletLoginInfo(String appletId, String secret, String code, String grantType) { + //请求地址 + String appletLoginUrl = APPLET_LOGIN_URL + + "?appid=" + appletId + + "&secret=" + secret + + "&js_code=" + code + + "&grant_type=" + grantType; + //发送请求 + String result = HttpUtils.sendGet(appletLoginUrl); + if (StringUtils.isBlank(result)) { + throw new ServiceException("获取微信小程序用户信息失败"); + } + return JSON.parseObject(result, AppletLoginVO.class); + } + + /** + * 获取微信小程序的accessToken + * + * @param appletId 小程序id + * @param secret 小程序秘钥 + * @return accessToken信息 + */ + public static AppletAccessToken getAppletAccessToken(String appletId, String secret) { + //请求路径 + String accessTokenUrl = ACCESS_TOKEN_URL + "&appid=" + appletId + "&secret=" + secret; + //发送请求 + String result = HttpUtils.sendGet(accessTokenUrl); + if (StringUtils.isBlank(result)) { + throw new ServiceException("获取微信小程序accessToken信息失败"); + } + return JSON.parseObject(result, AppletAccessToken.class); + } + + /** + * 获取微信小程序的手机号码 + * + * @param code 登录凭证 + * @param accessToken 小程序accessToken + * @return 手机信息 + */ + public static AppletPhoneVO getAppletPhoneInfo(String code, String accessToken) { + //请求地址 + String phoneUrl = PHONE_NUMBER_URL + accessToken; + //请求参数 + Map paramMap = new HashMap<>(); + paramMap.put("code", code); + String param = JSON.toJSONString(paramMap); + //发送POST请求 + String result = HttpUtils.sendPostJson(phoneUrl, param); + if (StringUtils.isBlank(result)) { + throw new ServiceException("获取微信小程序手机号失败"); + } + return JSON.parseObject(result, AppletPhoneVO.class); + } + + + /** + * 递归获取小程序二维码信息 + * + * @param weChatCodeId 输入id + * @param failFlag 调用失败标识 + * @param fileName 文件名称 + * @param appletPageUrl 二维码跳转路径 + * @param filePathWeChatCodeUrl 文件保存路径 + * @return java.lang.String 二维码地址 + */ + public static String createAppletCode(Long weChatCodeId, boolean failFlag, String fileName, String appletPageUrl, String filePathWeChatCodeUrl) { + if (BooleanUtils.isTrue(failFlag)) { + //删除原有Redis中的key值 + String accessTokenKey = Constants.EXAM_APPLET_ACCESS_TOKEN + "accessToken"; + Object object = redisTemplate.opsForValue().get(accessTokenKey); + if (Objects.nonNull(object)) { + redisTemplate.delete(accessTokenKey); + } + } + //删除原有Redis中缓存的AccessToken + String appletAccessToken = getAppletAccessToken(); + if (StringUtils.isBlank(appletAccessToken)) { + throw new ServiceException("获取小程序凭证信息失败,请联系管理员!"); + } + Map params = new HashMap<>(); + params.put("scene", weChatCodeId); + params.put("page", appletPageUrl); + String body = JSON.toJSONString(params); + String filePath = XinYiLuConfig.getProfile() + filePathWeChatCodeUrl + "/" + weChatCodeId; + //调用微信接口获取小程序二维码并将其上传到服务器中 + return getAppletCodePicture(appletAccessToken, body, filePath, fileName); + } + + /** + * 根据小程序AccessToken获取小程序的二维码图片 + * + * @param accessToken 微信小程序的accessToken + * @param body 接口传输参数 + * @param filePath 文件路径 + * @param fileName 文件名称 + * @return 小程序的二维码图片 + */ + public static String getAppletCodePicture(String accessToken, String body, String filePath, String fileName) { + CloseableHttpClient httpClient = HttpClientBuilder.create().build(); + HttpPost httpPost = new HttpPost(GET_APPLET_CODE_URL + accessToken); + try { + StringEntity entity = new StringEntity(body, "UTF-8"); + entity.setContentType("image/png"); + httpPost.setEntity(entity); + HttpResponse response = httpClient.execute(httpPost); + String contentType = response.getEntity().getContentType().toString(); + if (StringUtils.equals(contentType, JSON_DATA_FORMAT)) { + return FAIL; + } + String absolutePath = getAbsoluteFile(filePath, fileName).getAbsolutePath(); + try (InputStream inputStream = response.getEntity().getContent(); FileOutputStream out = new FileOutputStream(absolutePath)) { + byte[] buffer = new byte[1024]; + int len; + while ((len = inputStream.read(buffer)) != -1) { + out.write(buffer, 0, len); + } + out.flush(); + } + return absolutePath; + } catch (Exception e) { + log.error("获取小程序二维码接口调用失败,失败原因为:{}", e.getMessage()); + return null; + } + } + + /** + * 创建文件路径 + * + * @param uploadDir 路径名称 + * @param fileName 文件名称 + * @return File 文件目录 + */ + public static File getAbsoluteFile(String uploadDir, String fileName) { + File file = new File(uploadDir + File.separator + fileName); + if (!file.exists()) { + if (!file.getParentFile().exists()) { + boolean mkdirs = file.getParentFile().mkdirs(); + if (BooleanUtils.isFalse(mkdirs)) { + throw new ServiceException("文件路径创建失败,请联系管理员!"); + } + } + } + return file; + } + + + /** + * 获取小程序AccessToken方法 + * + * @return 小程序的AccessToken + */ + public static String getAppletAccessToken() { + String accessToken; + String accessTokenKey = Constants.EXAM_APPLET_ACCESS_TOKEN + "accessToken"; + //从Redis中取出accessToken + Object object = redisTemplate.opsForValue().get(accessTokenKey); + if (Objects.isNull(object)) { + //没有,获取accessToken + AppletAccessToken appletAccessToken = AppletChatUtil.getAppletAccessToken(appletChatConfig.getAppletId(), appletChatConfig.getSecret()); + if (Objects.isNull(appletAccessToken)) { + throw new ServiceException("获取微信小程序accessToken信息失败"); + } + if (Objects.nonNull(appletAccessToken.getErrcode()) && appletAccessToken.getErrcode() != SUCCESS_CODE) { + throw new ServiceException("获取微信小程序accessToken信息失败,失败信息为:" + appletAccessToken.getErrmsg()); + } + if (StringUtils.isBlank(appletAccessToken.getAccessToken())) { + throw new ServiceException("accessToken信息为空"); + } + //存入Redis中 + redisTemplate.opsForValue().set(accessTokenKey, appletAccessToken.getAccessToken(), 3600, TimeUnit.SECONDS); + accessToken = appletAccessToken.getAccessToken(); + } else { + accessToken = (String) object; + } + return accessToken; + } +} diff --git a/exam-admin/src/main/java/com/yf/exam/core/utils/http/HttpHelper.java b/exam-admin/src/main/java/com/yf/exam/core/utils/http/HttpHelper.java new file mode 100644 index 0000000..1f8ac70 --- /dev/null +++ b/exam-admin/src/main/java/com/yf/exam/core/utils/http/HttpHelper.java @@ -0,0 +1,43 @@ +package com.yf.exam.core.utils.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import javax.servlet.ServletRequest; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 通用http工具封装 + * + * @author xinyilu + */ +public class HttpHelper { + private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class); + + public static String getBodyString(ServletRequest request) { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + try (InputStream inputStream = request.getInputStream()) { + reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + String line = ""; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + } catch (IOException e) { + LOGGER.warn("getBodyString出现问题!"); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + LOGGER.error(ExceptionUtils.getMessage(e)); + } + } + } + return sb.toString(); + } +} diff --git a/exam-admin/src/main/java/com/yf/exam/core/utils/http/HttpUtils.java b/exam-admin/src/main/java/com/yf/exam/core/utils/http/HttpUtils.java new file mode 100644 index 0000000..fb9fd26 --- /dev/null +++ b/exam-admin/src/main/java/com/yf/exam/core/utils/http/HttpUtils.java @@ -0,0 +1,297 @@ +package com.yf.exam.core.utils.http; + +import com.yf.exam.constant.Constants; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.security.cert.X509Certificate; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.Consts; +import org.apache.http.HttpEntity; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.utils.HttpClientUtils; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 通用http发送方法 + * + * @author xinyilu + */ +public class HttpUtils { + private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url) { + return sendGet(url, StringUtils.EMPTY); + } + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param) { + return sendGet(url, param, Constants.UTF8); + } + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @param contentType 编码类型 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param, String contentType) { + StringBuilder result = new StringBuilder(); + BufferedReader in = null; + try { + String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url; + log.info("sendGet - {}", urlNameString); + URL realUrl = new URL(urlNameString); + URLConnection connection = realUrl.openConnection(); + connection.setRequestProperty("accept", "*/*"); + connection.setRequestProperty("connection", "Keep-Alive"); + connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + connection.connect(); + in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType)); + String line; + while ((line = in.readLine()) != null) { + result.append(line); + } + log.info("recv - {}", result); + } catch (ConnectException e) { + log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e); + } catch (SocketTimeoutException e) { + log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e); + } catch (IOException e) { + log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e); + } catch (Exception e) { + log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (Exception ex) { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + /** + * 向指定 URL 发送POST方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendPost(String url, String param) { + PrintWriter out = null; + BufferedReader in = null; + StringBuilder result = new StringBuilder(); + try { + log.info("sendPost - {}", url); + URL realUrl = new URL(url); + URLConnection conn = realUrl.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + conn.setDoOutput(true); + conn.setDoInput(true); + out = new PrintWriter(conn.getOutputStream()); + out.print(param); + out.flush(); + in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); + String line; + while ((line = in.readLine()) != null) { + result.append(line); + } + log.info("recv - {}", result); + } catch (ConnectException e) { + log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); + } catch (SocketTimeoutException e) { + log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } catch (IOException e) { + log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); + } catch (Exception e) { + log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); + } finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (IOException ex) { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + public static String sendSSLPost(String url, String param) { + StringBuilder result = new StringBuilder(); + String urlNameString = url + "?" + param; + try { + log.info("sendSSLPost - {}", urlNameString); + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom()); + URL console = new URL(urlNameString); + HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + conn.setDoOutput(true); + conn.setDoInput(true); + + conn.setSSLSocketFactory(sc.getSocketFactory()); + conn.setHostnameVerifier(new TrustAnyHostnameVerifier()); + conn.connect(); + InputStream is = conn.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + String ret = ""; + while ((ret = br.readLine()) != null) { + if (ret != null && !"".equals(ret.trim())) { + result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)); + } + } + log.info("recv - {}", result); + conn.disconnect(); + br.close(); + } catch (ConnectException e) { + log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e); + } catch (SocketTimeoutException e) { + log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } catch (IOException e) { + log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e); + } catch (Exception e) { + log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e); + } + return result.toString(); + } + + private static class TrustAnyTrustManager implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[]{}; + } + } + + private static class TrustAnyHostnameVerifier implements HostnameVerifier { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + } + + /** + * 将通知参数转化为字符串 + * + * @param request 请求信息 + * @return 请求参数信息 + */ + public static String readRequestData(HttpServletRequest request) { + BufferedReader br = null; + try { + StringBuilder result = new StringBuilder(); + br = request.getReader(); + for (String line; (line = br.readLine()) != null; ) { + if (result.length() > 0) { + result.append("\n"); + } + result.append(line); + } + return result.toString(); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + + /** + * 发送json格式的POST类型的http请求 + * + * @param url 请求地址 + * @param param json格式的请求参数 + * @return String 返回值信息 + */ + public static String sendPostJson(String url, String param) { + String result = null; + CloseableHttpClient httpClient = null; + CloseableHttpResponse response = null; + try { + httpClient = HttpClients.createDefault(); + // 字符串编码 + StringEntity entity = new StringEntity(param, Consts.UTF_8); + // 设置content-type + entity.setContentType("application/json"); + HttpPost httpPost = new HttpPost(url); + httpPost.setConfig(RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000).build()); + // 防止被当成攻击添加的 + httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/54.0.2840.87 Safari/537.36"); + // 接收参数设置 + httpPost.setHeader("Accept", "application/json"); + httpPost.setEntity(entity); + response = httpClient.execute(httpPost); + HttpEntity httpEntity = response.getEntity(); + result = EntityUtils.toString(httpEntity); + } catch (Exception e) { + log.warn("post请求发送失败,请求路径:[{}],请求参数:[{}],异常信息:[{}]", url, param, e); + } finally { + HttpClientUtils.closeQuietly(response); + HttpClientUtils.closeQuietly(httpClient); + } + return result; + } +} diff --git a/exam-admin/src/main/java/com/yf/exam/core/utils/regex/RegexUtil.java b/exam-admin/src/main/java/com/yf/exam/core/utils/regex/RegexUtil.java new file mode 100644 index 0000000..8b30a57 --- /dev/null +++ b/exam-admin/src/main/java/com/yf/exam/core/utils/regex/RegexUtil.java @@ -0,0 +1,62 @@ +package com.yf.exam.core.utils.regex; + +import com.yf.exam.core.exception.ServiceException; +import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +/** + * @Description 正则表达式工具类 + * @Author 纪寒 + * @Date 2022-08-24 13:58:48 + * @Version 1.0 + */ +@Component +public class RegexUtil { + + /** + * 校验手机号码 + * + * @param phone 手机号码 + * @return 校验结果 + */ + public boolean regexPhone(String phone) { + if (StringUtils.isBlank(phone)) { + throw new ServiceException("手机号码不能为空!"); + } + //校验手机号 + String regex = "^(((13[0-9]{1})|(14[0-9]{1})|(15[0-9]{1})|(16[2567]{1})|(17[0-9]{1})|(18[0-9]{1})|(19[0-9]{1}))+\\d{8})$"; + return Pattern.matches(regex, phone); + } + + /** + * 校验身份证号码 + * + * @param cardNo 身份证号码 + * @return 校验结果 + */ + public boolean regexCardNo(String cardNo) { + if (StringUtils.isBlank(cardNo)) { + throw new ServiceException("身份证号不能为空!"); + } + //校验身份证号码 + String regex = "(^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|" + + "(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}$)"; + return Pattern.matches(regex, cardNo); + } + + /** + * 校验座机手机号码 + * + * @param seatNumber 座机手机号码 + * @return 校验结果 + */ + public boolean regexSeatNumber(String seatNumber) { + if (StringUtils.isBlank(seatNumber)) { + throw new ServiceException("手机号码不能为空!"); + } + //校验座机手机号 + String regex = "^(([0-9]{4,13}))"; + return Pattern.matches(regex, seatNumber); + } +} diff --git a/exam-admin/src/main/java/com/yf/exam/modules/Constant.java b/exam-admin/src/main/java/com/yf/exam/modules/Constant.java deleted file mode 100644 index 20c5726..0000000 --- a/exam-admin/src/main/java/com/yf/exam/modules/Constant.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.yf.exam.modules; - - -/** - * 通用常量 - * @author bool - */ -public class Constant { - - /** - * 会话 - */ - public static final String TOKEN = "token"; -} diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/controller/AppletLoginController.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/controller/AppletLoginController.java new file mode 100644 index 0000000..50a37d9 --- /dev/null +++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/controller/AppletLoginController.java @@ -0,0 +1,92 @@ +package com.yf.exam.modules.applet.controller; + +import com.yf.exam.core.api.controller.BaseController; +import com.yf.exam.core.domain.AjaxResult; +import com.yf.exam.core.utils.regex.RegexUtil; +import com.yf.exam.modules.applet.service.AppletLoginService; +import com.yf.exam.modules.sys.user.dto.SysUserDTO; +import com.yf.exam.modules.sys.user.service.SysUserService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import javax.annotation.Resource; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +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; + +/** + * @description: 微信小程序登录注册控制器 + * @author: haown + * @create: 2025-08-11 16:05 + **/ +@Api(tags={"微信小程序"}) +@RestController +@RequestMapping("/examApplet") +public class AppletLoginController extends BaseController { + + @Resource + private AppletLoginService appletLoginService; + + @Resource + private SysUserService sysUserService; + @Resource + private RegexUtil regexUtil; + + /** + * 注册用户信息 + * + * @param reqDTO 输入参数 + * @return 结果 + */ + @ApiOperation(value = "注册用户信息") + @PostMapping("/register") + public AjaxResult registerUserInfo(@RequestBody SysUserDTO reqDTO) { + //校验手机号 + boolean regexPhone = regexUtil.regexPhone(StringUtils.isBlank(reqDTO.getPhone()) ? "" : reqDTO.getPhone()); + if (BooleanUtils.isFalse(regexPhone)) { + return AjaxResult.error("您输入的手机号不正确,请重新输入!"); + } + //校验身份证号 + boolean cardNo = regexUtil.regexCardNo(StringUtils.isBlank(reqDTO.getUserName()) ? "" : reqDTO.getUserName()); + if (BooleanUtils.isFalse(cardNo)) { + return AjaxResult.error("您输入的身份证号不正确,请重新输入!"); + } + return AjaxResult.success(sysUserService.reg(reqDTO)); + } + + /** + * 根据登录凭证获取用户的登录信息 + * + * @param loginCode 登录凭证 + * @return 微信用户登录信息 + */ + @ApiOperation(value = "根据登录凭证获取用户的登录信息") + @GetMapping("/appletLogin") + public AjaxResult appletLogin(@RequestParam("loginCode") String loginCode) { + if (StringUtils.isBlank(loginCode)) { + return AjaxResult.error("登录凭证编码不能为空!"); + } + //if (StringUtils.isBlank(phoneCode)) { + // return AjaxResult.error("获取手机号凭证不存在"); + //} + return appletLoginService.appletLogin(loginCode); + } + + /** + * 根据openid判断当前用户信息是否存在,微信小程序登录页使用 + * + * @param openId 微信用户的openid + * @return 标识,LOGIN:已登录,NOT_LOGIN:未登录 + */ + @GetMapping("/existUserInfo") + public AjaxResult existUserInfo(String openId) { + if (StringUtils.isBlank(openId)) { + return AjaxResult.error("用户微信唯一标识不存在!"); + } + return appletLoginService.existUserInfo(openId); + } +} diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/entity/AppletAccessToken.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/entity/AppletAccessToken.java new file mode 100644 index 0000000..99b9cc0 --- /dev/null +++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/entity/AppletAccessToken.java @@ -0,0 +1,43 @@ +package com.yf.exam.modules.applet.entity; + +import com.alibaba.fastjson2.annotation.JSONField; +import java.io.Serializable; +import lombok.Data; + +/** + * @Description 微信小程序accessToken信息实体类 + * @Author 纪寒 + * @Date 2022-08-17 17:05:10 + * @Version 1.0 + */ +@Data +public class AppletAccessToken implements Serializable { + private static final long serialVersionUID = 7351002130502802480L; + /** + * accessToken值 + */ + @JSONField(name = "access_token") + private String accessToken; + + /** + * access_token有效时间,有效时间为7200秒 + */ + @JSONField(name = "expires_in") + private String expiresIn; + + /** + * 错误状态码 + * 40001:AppSecret 错误或者 AppSecret 不属于这个小程序,请开发者确认 AppSecret 的正确性 + * 40002:请确保 grant_type 字段值为 client_credential + * 40013:不合法的 AppID,请开发者检查 AppID 的正确性,避免异常字符,注意大小写 + */ + private Integer errcode; + + /** + * 错误状态值 + * 40001:AppSecret 错误或者 AppSecret 不属于这个小程序,请开发者确认 AppSecret 的正确性 + * 40002:请确保 grant_type 字段值为 client_credential + * 40013:不合法的 AppID,请开发者检查 AppID 的正确性,避免异常字符,注意大小写 + */ + private String errmsg; +} diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/service/AppletLoginService.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/AppletLoginService.java new file mode 100644 index 0000000..dfb89ca --- /dev/null +++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/AppletLoginService.java @@ -0,0 +1,27 @@ +package com.yf.exam.modules.applet.service; + +import com.yf.exam.core.domain.AjaxResult; + +/** + * @description: 微信小程序登录注册业务层 + * @author: haown + * @create: 2025-08-11 16:05 + **/ +public interface AppletLoginService { + + /** + * 根据登录凭证获取用户的登录信息 + * + * @param loginCode 登录凭证 + * @return 微信用户登录信息 + */ + AjaxResult appletLogin(String loginCode); + + /** + * 根据openid判断当前用户信息是否存在,微信小程序登录页使用 + * + * @param openId 微信用户的openid + * @return 用户列表 + */ + AjaxResult existUserInfo(String openId); +} diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/service/impl/AppletLoginServiceImpl.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/impl/AppletLoginServiceImpl.java new file mode 100644 index 0000000..b43caed --- /dev/null +++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/impl/AppletLoginServiceImpl.java @@ -0,0 +1,147 @@ +package com.yf.exam.modules.applet.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.yf.exam.config.AppletChatConfig; +import com.yf.exam.constant.Constants; +import com.yf.exam.core.domain.AjaxResult; +import com.yf.exam.core.utils.AppletChatUtil; +import com.yf.exam.modules.applet.service.AppletLoginService; +import com.yf.exam.modules.applet.vo.AppletLoginVO; +import com.yf.exam.modules.sys.user.entity.SysUser; +import com.yf.exam.modules.sys.user.service.SysUserService; +import com.yf.exam.modules.utils.AppletAccessTokenUtil; +import java.util.Date; +import java.util.Objects; +import javax.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +/** + * @Description 微信小程序登录注册业务层实现类 + * @Author 纪寒 + * @Date 2022-09-02 10:55:29 + * @Version 1.0 + */ +@Service +@Slf4j +public class AppletLoginServiceImpl implements AppletLoginService { + + @Resource + private RedisTemplate redisTemplate; + @Resource + private AppletChatConfig appletChatConfig; + @Resource + private AppletAccessTokenUtil appletAccessTokenUtil; + @Resource + private SysUserService sysUserService; + + /** + * 返回成功状态码 + */ + private static final int SUCCESS_CODE = 0; + + /** + * 返回成功状态码 + */ + private static final String OK = "ok"; + + /** + * 获取微信小程序access_token错误码 + */ + private static final int ERROR_ACCESS_CODE = 40001; + + + /** + * 根据登录凭证获取用户的登录信息 + * + * @param loginCode 登录凭证 + * @return 微信用户登录信息 + */ + @Override + public AjaxResult appletLogin(String loginCode) { + //根据code获取用户的微信unionId以及openId等信息 + AppletLoginVO appletLoginInfo = AppletChatUtil.getAppletLoginInfo(appletChatConfig.getAppletId(), appletChatConfig.getSecret(), loginCode, appletChatConfig.getGrantType()); + if (Objects.isNull(appletLoginInfo)) { + return AjaxResult.error("获取微信小程序用户信息失败"); + } + if (Objects.nonNull(appletLoginInfo.getErrcode()) && appletLoginInfo.getErrcode() != SUCCESS_CODE) { + return AjaxResult.error("获取微信小程序用户信息失败,失败信息为:" + appletLoginInfo.getErrmsg()); + } + //获取微信accessToken + String accessToken; + String accessTokenKey = Constants.EXAM_APPLET_ACCESS_TOKEN + "accessToken"; + //从Redis中取出accessToken + Object object = redisTemplate.opsForValue().get(accessTokenKey); + if (Objects.isNull(object)) { + //没有,获取accessToken + accessToken = appletAccessTokenUtil.getAppletAccessToken(); + } else { + accessToken = (String) object; + } + //获取用户手机号 + + //AppletPhoneVO appletPhoneInfo = AppletChatUtil.getAppletPhoneInfo(phoneCode, accessToken); + //if (Objects.isNull(appletPhoneInfo)) { + // return AjaxResult.error("获取用户手机号失败"); + //} + //if (Objects.nonNull(appletPhoneInfo.getErrcode()) && appletPhoneInfo.getErrcode() == ERROR_ACCESS_CODE) { + // //当前Redis缓存中的access_token无效直接删除 + // if (Objects.nonNull(object)) { + // redisTemplate.delete(accessTokenKey); + // //删除之后重新获取获取accessToken + // accessToken = appletAccessTokenUtil.getAppletAccessToken(); + // appletPhoneInfo = AppletChatUtil.getAppletPhoneInfo(phoneCode, accessToken); + // if (Objects.isNull(appletPhoneInfo)) { + // return AjaxResult.error("获取用户手机号失败"); + // } + // if (Objects.nonNull(appletPhoneInfo.getErrcode()) && appletPhoneInfo.getErrcode() == ERROR_ACCESS_CODE) { + // return AjaxResult.error("登录失败!"); + // } + // } + //} + //if (StringUtils.isNotBlank(appletPhoneInfo.getErrmsg()) && !OK.equals(appletPhoneInfo.getErrmsg())) { + // return AjaxResult.error("获取用户手机号失败,失败信息为:" + appletPhoneInfo.getErrmsg()); + //} + // 根据手机号和openid判断当前用户是否存在 + //String phone = StringUtils.isBlank(appletPhoneInfo.getPhoneInfo().getPhoneNumber()) ? "" : appletPhoneInfo.getPhoneInfo().getPhoneNumber(); + String openId = StringUtils.isBlank(appletLoginInfo.getOpenid()) ? "" : appletLoginInfo.getOpenid(); + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + //.eq(SysUser::getPhone, phone) + .eq(SysUser::getOpenid, openId); + SysUser userByPhone = sysUserService.getOne(wrapper, false); + + SysUser user = new SysUser(); + // 考生信息为空,返回openId + if (Objects.isNull(userByPhone)) { + return AjaxResult.success("您还未注册,请注册后使用!", openId); + } + //更新用户的openid等微信标识信息 + user.setId(userByPhone.getId()); + user.setOpenid(openId); + user.setPhone(StringUtils.isBlank(userByPhone.getPhone()) ? "" : userByPhone.getPhone()); + user.setUpdateTime(new Date()); + sysUserService.updateById(user); + return AjaxResult.success(user); + } + + /** + * 根据openid判断当前用户信息是否存在,微信小程序登录页使用 + * + * @param openId 微信用户的openid + * @return + */ + @Override + public AjaxResult existUserInfo(String openId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUser::getOpenid, openId); + SysUser user = sysUserService.getOne(wrapper, false); + if (Objects.nonNull(user) && StringUtils.isNotBlank(user.getOpenid())) { + return AjaxResult.success("LOGIN"); + } + return AjaxResult.success("NOT_LOGIN"); + } + +} diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/AppletLoginVO.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/AppletLoginVO.java new file mode 100644 index 0000000..782891c --- /dev/null +++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/AppletLoginVO.java @@ -0,0 +1,39 @@ +package com.yf.exam.modules.applet.vo; + +import java.io.Serializable; +import lombok.Data; + +/** + * @Description 微信小程序用户信息实体类 + * @Author 纪寒 + * @Date 2022-08-17 16:12:22 + * @Version 1.0 + */ +@Data +public class AppletLoginVO implements Serializable { + private static final long serialVersionUID = 3407837292456224369L; + + /** + * 小程序unionid + */ + private String unionid; + + /** + * 小程序openid + */ + private String openid; + + /** + * 错误状态码,40029:js_code无效,45011:API 调用太频繁,请稍候再试 + * 40226:高风险等级用户,小程序登录拦截 ,-1:系统繁忙,此时请开发者稍候再试 + */ + private Integer errcode; + + /** + * 状态信息,取值有,40029:code 无效, + * 45011:api minute-quota reach limit mustslower retry next minute + * 40226:code blocked + * -1:system error + */ + private String errmsg; +} diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/AppletPhoneVO.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/AppletPhoneVO.java new file mode 100644 index 0000000..1ae309d --- /dev/null +++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/AppletPhoneVO.java @@ -0,0 +1,85 @@ +package com.yf.exam.modules.applet.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @Description 获取小程序用户手机号实体类 + * @Author 纪寒 + * @Date 2022-08-17 17:44:53 + * @Version 1.0 + */ +@NoArgsConstructor +@Data +public class AppletPhoneVO implements Serializable { + private static final long serialVersionUID = -135523900524315866L; + + /** + * 错误编码, + * 0:成功 + * -1:系统繁忙,此时请开发者稍候再试 + * 40029:不合法的code(code不存在、已过期或者使用过) + */ + @JsonProperty("errcode") + private Integer errcode; + + /** + * 返回提示信息,ok:成功 + */ + @JsonProperty("errmsg") + private String errmsg; + + /** + * 电话信息实体类 + */ + @JsonProperty("phone_info") + private PhoneInfoDTO phoneInfo; + + @NoArgsConstructor + @Data + public static class PhoneInfoDTO { + + /** + * 用户绑定的手机号(国外手机号会有区号) + */ + @JsonProperty("phoneNumber") + private String phoneNumber; + + /** + * 没有区号的手机号 + */ + @JsonProperty("purePhoneNumber") + private String purePhoneNumber; + + /** + * 区号 + */ + @JsonProperty("countryCode") + private Integer countryCode; + + /** + * 数据水印 + */ + @JsonProperty("watermark") + private WatermarkDTO watermark; + + @NoArgsConstructor + @Data + public static class WatermarkDTO { + + /** + * 用户获取手机号操作的时间戳 + */ + @JsonProperty("timestamp") + private Integer timestamp; + + /** + * 小程序appid + */ + @JsonProperty("appid") + private String appid; + } + } +} diff --git a/exam-admin/src/main/java/com/yf/exam/modules/sys/user/controller/SysUserController.java b/exam-admin/src/main/java/com/yf/exam/modules/sys/user/controller/SysUserController.java index f570f0e..938ce0d 100644 --- a/exam-admin/src/main/java/com/yf/exam/modules/sys/user/controller/SysUserController.java +++ b/exam-admin/src/main/java/com/yf/exam/modules/sys/user/controller/SysUserController.java @@ -55,7 +55,7 @@ public class SysUserController extends BaseController { } /** - * 用户登录 + * 用户退出 * @return */ @CrossOrigin diff --git a/exam-admin/src/main/java/com/yf/exam/modules/sys/user/dto/SysUserDTO.java b/exam-admin/src/main/java/com/yf/exam/modules/sys/user/dto/SysUserDTO.java index 920983f..39ea2e6 100644 --- a/exam-admin/src/main/java/com/yf/exam/modules/sys/user/dto/SysUserDTO.java +++ b/exam-admin/src/main/java/com/yf/exam/modules/sys/user/dto/SysUserDTO.java @@ -40,6 +40,9 @@ public class SysUserDTO extends UserAttachment implements Serializable { @ApiModelProperty(value = "密码盐", required=true) private String salt; + @ApiModelProperty(value = "用户微信openid", required=true) + private String openid; + @ApiModelProperty(value = "角色列表", required=true) private String roleIds; diff --git a/exam-admin/src/main/java/com/yf/exam/modules/sys/user/dto/request/SysUserLoginReqDTO.java b/exam-admin/src/main/java/com/yf/exam/modules/sys/user/dto/request/SysUserLoginReqDTO.java index 35f00a7..3a14d6c 100644 --- a/exam-admin/src/main/java/com/yf/exam/modules/sys/user/dto/request/SysUserLoginReqDTO.java +++ b/exam-admin/src/main/java/com/yf/exam/modules/sys/user/dto/request/SysUserLoginReqDTO.java @@ -25,5 +25,8 @@ public class SysUserLoginReqDTO implements Serializable { @ApiModelProperty(value = "密码", required=true) private String password; + + @ApiModelProperty(value = "用户微信openid", required=true) + private String openid; } diff --git a/exam-admin/src/main/java/com/yf/exam/modules/sys/user/dto/response/SysUserLoginDTO.java b/exam-admin/src/main/java/com/yf/exam/modules/sys/user/dto/response/SysUserLoginDTO.java index 78fb157..904d6ec 100644 --- a/exam-admin/src/main/java/com/yf/exam/modules/sys/user/dto/response/SysUserLoginDTO.java +++ b/exam-admin/src/main/java/com/yf/exam/modules/sys/user/dto/response/SysUserLoginDTO.java @@ -35,6 +35,9 @@ public class SysUserLoginDTO extends UserAttachment implements Serializable { @ApiModelProperty(value = "手机号", required=true) private String phone; + @ApiModelProperty(value = "用户微信openid", required=true) + private String openid; + @ApiModelProperty(value = "角色列表", required=true) private String roleIds; diff --git a/exam-admin/src/main/java/com/yf/exam/modules/sys/user/entity/SysUser.java b/exam-admin/src/main/java/com/yf/exam/modules/sys/user/entity/SysUser.java index 5a72abd..b4e50c1 100644 --- a/exam-admin/src/main/java/com/yf/exam/modules/sys/user/entity/SysUser.java +++ b/exam-admin/src/main/java/com/yf/exam/modules/sys/user/entity/SysUser.java @@ -68,6 +68,11 @@ public class SysUser extends UserAttachment { @TableField("depart_id") private String departId; + /** + * 用户微信openid + */ + private String openid; + /** * 创建时间 */ diff --git a/exam-admin/src/main/java/com/yf/exam/modules/sys/user/service/impl/SysUserServiceImpl.java b/exam-admin/src/main/java/com/yf/exam/modules/sys/user/service/impl/SysUserServiceImpl.java index 2627b2a..fcd0009 100644 --- a/exam-admin/src/main/java/com/yf/exam/modules/sys/user/service/impl/SysUserServiceImpl.java +++ b/exam-admin/src/main/java/com/yf/exam/modules/sys/user/service/impl/SysUserServiceImpl.java @@ -142,8 +142,6 @@ public class SysUserServiceImpl extends ServiceImpl impl @Override public void update(SysUserDTO reqDTO) { - - String pass = reqDTO.getPassword(); if(!StringUtils.isBlank(pass)){ PassInfo passInfo = PassHandler.buildPassword(pass); diff --git a/exam-admin/src/main/java/com/yf/exam/modules/utils/AppletAccessTokenUtil.java b/exam-admin/src/main/java/com/yf/exam/modules/utils/AppletAccessTokenUtil.java new file mode 100644 index 0000000..15faf78 --- /dev/null +++ b/exam-admin/src/main/java/com/yf/exam/modules/utils/AppletAccessTokenUtil.java @@ -0,0 +1,64 @@ +package com.yf.exam.modules.utils; + +import com.yf.exam.config.AppletChatConfig; +import com.yf.exam.constant.Constants; +import com.yf.exam.core.exception.ServiceException; +import com.yf.exam.core.utils.AppletChatUtil; +import com.yf.exam.modules.applet.entity.AppletAccessToken; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import javax.annotation.Resource; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +/** + * @Description 获取小程序AccessToken公共方法 + * @Author 纪寒 + * @Date 2023-02-27 18:02:05 + * @Version 1.0 + */ +@Component +public class AppletAccessTokenUtil { + + @Resource + private RedisTemplate redisTemplate; + @Resource + private AppletChatConfig appletChatConfig; + /** + * 返回成功状态码 + */ + private static final int SUCCESS_CODE = 0; + + /** + * 获取小程序AccessToken方法 + * + * @return 小程序的AccessToken + */ + public String getAppletAccessToken() { + String accessToken; + String accessTokenKey = Constants.EXAM_APPLET_ACCESS_TOKEN + "accessToken"; + //从Redis中取出accessToken + Object object = redisTemplate.opsForValue().get(accessTokenKey); + if (Objects.isNull(object)) { + //没有,获取accessToken + AppletAccessToken appletAccessToken = AppletChatUtil.getAppletAccessToken(appletChatConfig.getAppletId(), appletChatConfig.getSecret()); + if (Objects.isNull(appletAccessToken)) { + throw new ServiceException("获取微信小程序accessToken信息失败"); + } + if (Objects.nonNull(appletAccessToken.getErrcode()) && appletAccessToken.getErrcode() != SUCCESS_CODE) { + throw new ServiceException("获取微信小程序accessToken信息失败,失败信息为:" + appletAccessToken.getErrmsg()); + } + if (StringUtils.isBlank(appletAccessToken.getAccessToken())) { + throw new ServiceException("accessToken信息为空"); + } + //存入Redis中 + redisTemplate.opsForValue().set(accessTokenKey, appletAccessToken.getAccessToken(), 3600, TimeUnit.SECONDS); + accessToken = appletAccessToken.getAccessToken(); + } else { + accessToken = (String) object; + } + return accessToken; + } + +} diff --git a/exam-admin/src/main/resources/apiclient_key.pem b/exam-admin/src/main/resources/apiclient_key.pem new file mode 100644 index 0000000..7965311 --- /dev/null +++ b/exam-admin/src/main/resources/apiclient_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQChOQsrP6xr7VpH +LB6kMpx0wB9QXio6Fk89GwD1rFnR/8Eym6mUbv9BR7NddJ3LL1UCSQ3b1DXuDz64 +/s8nwvJmIABsgBmKS7Krb3qd7ua2NL7KodjnQuj4X3NOUQ2bPh21y5sOk6cVOObM +4g2jvaPUk52y+kDXx/8IWLIms0hPqRkgCXVtTDrtgab4wABrfha3ifiC6qHuaeza +ogo9he09QfWKPlZyW/OkwON4i0mMI0lAUaMnfSJX5QBLFwZYUHgR/8H/aMRLXnOx +W/tz1bvV2SgKwpH7TalRu2Avl9t5M1lPWTbxl4vEV6em3ijqzldpIOljwJ20cL6g +BbgJm2Y9AgMBAAECggEAOaVU2gTtWLXIZtRerGUwTgp359uTi6t4b6fdIvqaLx0c +bkT8UIeTmzrQ5mSRGxp0cdm/K8/n7JPk4G6zkUsCMwPUQvdWqn1AiE3W8Ot/8LxR +T9Co5p+k/1HZv7H5hH2kT+FaMs8Wmd77n1xgdiCTmKmjZGBYmQ74oHpeULof6+NA +JX2LEMXBjSvcsEj2ppml1M9XjZzB7zPQPqVPGL5hTAqQpT179/iy8aPdzBZRKAFY +liajWYlRVMseu1S21PaOygNpRhvLj+LQB+dfQ+s5jYIhBvVjJ4mNFK+ro1KrbFEa +g2pKYFqcY9B4uOEB12K1bA4lxWPDjZvi5L71ncB8wQKBgQDTMDnOH+IQ/jrVbnX3 +YM24gKoJiKzbXcGSeg5jciGpAQ2JGtKw3xbZB54yXCwvJXssXkSfKmW9qKR299pG +TloaKHbhHxvX1jECpFmvRTLEhKb6uWcy779NFv+443nM/G0cB+vxhezj4tqE6Qqw +ijbDD+QS26Dm+ZfEQTaH1DaLbQKBgQDDbq8PEsL3uaoPg8gmyKJx9hmUW2nn3Tto +CgOI7PVQbvwOIoI8phuc1VLatnxjx3SeMasGoWoVk7/hc0ZXHvo0QsQWZt74McSC +mV2ub7L6BLyvrpsufEJZlUIiQXDIHDcucszgRaYJk4rJKmCh1R7c6dewAVE/TqTW +7GrwUBI0EQKBgBRSFZ3Rz4zXCY4z3MH63JCeQL4+GnPZJ6ESgYPsHXUHlufUXuWJ +8cbcRsqNt+qpbpqsT2oJSFThf0G7Q5N0QpM1xYqP1bwE7h5U9hQ5UlM6eF6zrExo +aT3fsqd1q+ifeVgzIu7QdiTPVTtouRCXnAFU7BssauUOqx3FMJwNPVpZAoGAYMZ9 +j6RFwbBB4z7prTLrJi2sywddcUDfOwzCZVqZu8PJsyIphejngYktZzq4bByHxhJo +U3c49ghdG0IfEeM4GQr62PEF9reGTPmvJ0MOyLnxyblYBPPpUz0TK61mMOGv/aNB +islSiCl0r1r50QmdJ93wParZVdUW0YrvBaNQ8FECgYAm1j0tfGsMmocMYSoeMGt5 +wRetXbkrKfMbNh6GALiNClEj6tFYKMbuQOuUzADYDxHHz/SPNozuP7Ax/NI95rtw +mGZcMx/KhKZQvZkAwCWUqRSn6cDPtqH4KNta7QZ8G36XtdPRP1a/h8g96cEiB/CV +zQPps/kR9HNAJfr0sBPEWA== +-----END PRIVATE KEY----- diff --git a/exam-admin/src/main/resources/application.yml b/exam-admin/src/main/resources/application.yml index 99b08ac..c18b736 100644 --- a/exam-admin/src/main/resources/application.yml +++ b/exam-admin/src/main/resources/application.yml @@ -28,20 +28,69 @@ server: enabled: true min-response-size: 10 mime-types: application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css - -# 新医路微信商户号配置参数 + # redis 配置 + redis: + # 地址 + host: localhost + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 2 + # 密码 + password: + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms +# 考试系统微信小程序参数配置信息 +exam-applet-chat-config: + # 微信小程序 + applet-id: wx26b9ecdba54bc588 + # 微信小程序密钥 + secret: ccd19a0bb2bc74f38b083c902daf48a7 + # 微信小程序返回国家语言 + lang: zh_CN + # 微信小程序授权类型 + grant-type: authorization_code + # 微信小程序事件回调令牌 + token: Yw3vfW1ILpc34qAVDtTpB2hesAMCpvW0 +# 山东省公共卫生学会微信商户号配置参数 xyl-we-chat-config: - # 新医路商户号 1633348407 山东新医路信息科技有限公司 山东柏杏新医健康服务有限公司 - xyl-mch-id: 1690248007 - # 新医路商户号API证书序号 7C6A18FC8E1F0445901B1BE1C4DD1ACE284C3D79 - xyl-mch-serial-no: 760D3316C2F3DF8D1DB05B56A37BFCD3C34EEDA8 - # 新医路商户私钥文件 - xyl-private-key-path: baixing_apiclient_key.pem - # 新医路API V3版本密钥 Xyl699003981qazVFR4xsw23edcASDFG - xyl-payment-key: baixingXINYIHULIZHAN202400000000 - # 新医路微信支付回调地址 https://quanyidaojia.xinelu.cn + # 山东省公共卫生学会商户号 1724506668 山东省公共卫生学会 + xyl-mch-id: 1724506668 + # 山东省公共卫生学会商户号API证书序号 2C1188B1E533743F24AA43D374118B5D4A4FABC1 + xyl-mch-serial-no: 2C1188B1E533743F24AA43D374118B5D4A4FABC1 + # 山东省公共卫生学会商户私钥文件 + xyl-private-key-path: apiclient_key.pem + # 山东省公共卫生学会API V3版本密钥 Xyl699003981qazVFR4xsw23edcASDFG + xyl-payment-key: gonggongweishengXUEHUI2025081100 + # 山东省公共卫生学会微信支付回调地址 https://quanyidaojia.xinelu.cn xyl-wechat-notify-url: http://8.131.93.145:54097 -# h5支付接口地址 +# 微信支付接口地址,包含小程序和App支付接口地址 we-chat-payment-url-config: - # h5下单接口地址 - h5-palce-order-url: https://api.mch.weixin.qq.com/v3/pay/transactions/h5 + # 小程序JSAPI下单接口地址 + jsapi-palce-order-url: https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi + # 微信支付订单号查询接口地址 + query-order-no-url: https://api.mch.weixin.qq.com/v3/pay/transactions/id/%s + # 商户订单号接口查询 + query-mch-id-url: https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s + # 关闭订单接口地址 + close-order-url: https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s/close + # 申请退款接口地址 + refund-apply-url: https://api.mch.weixin.qq.com/v3/refund/domestic/refunds + # 查询单笔退款接口地址 + refund-query-order-url: https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/%s + # 申请交易账单接口地址 + trade-apply-bill-url: https://api.mch.weixin.qq.com/v3/bill/tradebill + # 申请资金账单接口地址 + capital-apply-bill-url: https://api.mch.weixin.qq.com/v3/bill/fundflowbill + # App下单接口地址 + app-place-order-url: https://api.mch.weixin.qq.com/v3/pay/transactions/app diff --git a/exam-admin/src/main/resources/mapper/sys/user/SysUserMapper.xml b/exam-admin/src/main/resources/mapper/sys/user/SysUserMapper.xml index 1f0fd2e..abb4bc6 100644 --- a/exam-admin/src/main/resources/mapper/sys/user/SysUserMapper.xml +++ b/exam-admin/src/main/resources/mapper/sys/user/SysUserMapper.xml @@ -29,13 +29,14 @@ + `id`,`user_name`,`real_name`,phone,`password`,`salt`,`role_ids`,`depart_id`,`create_time`,`update_time`,`state`, `email`,`address`,`education`,`graduate_school`,`major`,`reg_type`,`train_institution`,`train_start_date`,`train_end_date`, - `card_front`,`card_back`,`card_copy`,`photo`,`certificate`,`physical_report` + `card_front`,`card_back`,`card_copy`,`photo`,`certificate`,`physical_report`,`openid`