微信支付、回调、订单查询;

微信用户登录、用户信息查询、修改用户信息、同意用户协议;
文件OSS上传、删除接口;
This commit is contained in:
songmingsong 2024-12-05 17:33:25 +08:00
parent 4822174c5e
commit ffc9fcb95c
39 changed files with 2074 additions and 133 deletions

View File

@ -33,7 +33,12 @@
<dependencies>
<!-- 微信支付-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.12</version>
</dependency>
<!-- 引入aop相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -0,0 +1,29 @@
package com.ycwl.basic.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 阿里云OSS配置
*
* @author songmingsong
**/
@Data
@Component
public class OssConfig {
@Value("${aliYunOss.endpoint}")
private String endPoint;
@Value("${aliYunOss.accessKeyId}")
private String accessKeyId;
@Value("${aliYunOss.accessKeySecret}")
private String accessKeySecret;
@Value("${aliYunOss.bucketName}")
private String bucketName;
@Value("${aliYunOss.objectName}")
private String objectName;
@Value("${aliYunOss.url}")
private String url;
@Value("${aliYunOss.region}")
private String region;
}

View File

@ -0,0 +1,74 @@
package com.ycwl.basic.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 微信小程序配置
*
* @author songmingsong
**/
@Data
@Component
@ConfigurationProperties(prefix = "wx")
public class WechatConfig {
/**
* 公众号的appId
*/
private String appId;
/**
* 公众号的密钥
*/
private String appSecret;
/**
* 公众号推送模板
*/
@Value("${wx.push.templateId}")
private String templateId;
/**
* 小程序的AppId
*/
private String miniProgramAppId;
/**
* 小程序的secret
*/
private String miniProgramSecret;
/**
* 申请openid授权
*/
private String grandType;
/**
* 商户号
*/
private String mchId;
/**
* 商户证书序列号
*/
private String mchSerialNo;
/**
* 回调接口地址
*/
private String notifyUrl;
/**
* 商户API私钥路径
*/
private String keyPath;
/**
* 商户APIV3密钥
*/
private String apiV3;
}

View File

@ -0,0 +1,62 @@
package com.ycwl.basic.constant;
/**
* <p>数字常量类</p>
*
* @author songmingsong
* @Description 数字常量类
*/
public interface NumberConstant {
int NEGATIVE_ONE = -1;
int NEGATIVE_TWO = -2;
int ZERO = 0;
int ONE = 1;
double ONE_DOUBLE = 1.0;
int TWO = 2;
int THREE = 3;
int FOUR = 4;
int FIVE = 5;
int SIX = 6;
int SEVEN = 7;
int TEN = 10;
int ELEVEN = 11;
int SIXTEEN = 16;
int SEVENTEEN = 17;
int EIGHTEEN = 18;
int NINETEEN = 19;
int TWENTY = 20;
int TWENTY_FOUR = 24;
int TWENTY_FIVE = 25;
int THIRTY = 30;
int FORTY = 40;
int SIXTY = 60;
int HUNDRED = 100;
int THOUSAND = 1000;
int MILLION = 1000000;
}

View File

@ -0,0 +1,82 @@
package com.ycwl.basic.constant;
import com.wechat.pay.java.service.payments.model.Transaction;
import java.util.HashMap;
import java.util.Map;
/**
* <p>@description: 微信常量 </p>
* <p>@author: songmingsong </p>
**/
public class WeiXinConstant {
private static final Map<Transaction.TradeStateEnum, String> STATE_DESCRIPTION_MAP = new HashMap<>();
static {
STATE_DESCRIPTION_MAP.put(com.wechat.pay.java.service.payments.model.Transaction.TradeStateEnum.SUCCESS, "SUCCESS");
STATE_DESCRIPTION_MAP.put(com.wechat.pay.java.service.payments.model.Transaction.TradeStateEnum.REFUND, "REFUND");
STATE_DESCRIPTION_MAP.put(com.wechat.pay.java.service.payments.model.Transaction.TradeStateEnum.NOTPAY, "NOTPAY");
STATE_DESCRIPTION_MAP.put(com.wechat.pay.java.service.payments.model.Transaction.TradeStateEnum.CLOSED, "CLOSED");
STATE_DESCRIPTION_MAP.put(com.wechat.pay.java.service.payments.model.Transaction.TradeStateEnum.REVOKED, "REVOKED");
STATE_DESCRIPTION_MAP.put(com.wechat.pay.java.service.payments.model.Transaction.TradeStateEnum.USERPAYING, "USERPAYING");
STATE_DESCRIPTION_MAP.put(com.wechat.pay.java.service.payments.model.Transaction.TradeStateEnum.PAYERROR, "PAYERROR");
STATE_DESCRIPTION_MAP.put(com.wechat.pay.java.service.payments.model.Transaction.TradeStateEnum.ACCEPT, "ACCEPT");
}
public static String getDescriptionState(com.wechat.pay.java.service.payments.model.Transaction.TradeStateEnum state) {
return STATE_DESCRIPTION_MAP.getOrDefault(state, "未知状态");
}
private static final Map<com.wechat.pay.java.service.payments.model.Transaction.TradeTypeEnum, String> STATE_DESCRIPTION_MAP_TYPE = new HashMap<>();
static {
STATE_DESCRIPTION_MAP_TYPE.put(Transaction.TradeTypeEnum.JSAPI, "JSAPI");
STATE_DESCRIPTION_MAP_TYPE.put(Transaction.TradeTypeEnum.NATIVE, "NATIVE");
STATE_DESCRIPTION_MAP_TYPE.put(Transaction.TradeTypeEnum.APP, "APP");
STATE_DESCRIPTION_MAP_TYPE.put(Transaction.TradeTypeEnum.MICROPAY, "MICROPAY");
STATE_DESCRIPTION_MAP_TYPE.put(Transaction.TradeTypeEnum.MWEB, "MWEB");
STATE_DESCRIPTION_MAP_TYPE.put(Transaction.TradeTypeEnum.FACEPAY, "FACEPAY");
}
public static String getDescriptionType(Transaction.TradeTypeEnum type) {
return STATE_DESCRIPTION_MAP_TYPE.getOrDefault(type, "未知类型");
}
/**
* 公众号模板地址
*/
public static final String PUBLIC_ACCOUNT_TEMPLATE =
"https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=";
/**
* 获取微信用户基本信息地址
*/
public static final String WECHAT_OAUTH_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token";
/**
* 获取ACCESS_TOKEN
*/
public static final String ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token";
/**
* 登录凭证校验
*/
public static final String GET_OPEN_ID = "https://api.weixin.qq.com/sns/jscode2session";
/**
* 获取小程序地址
*/
public static final String GET_MINI_QRCODE = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s";
/**
* 获取用户基本信息
*/
public static final String GET_USER_BASIC_INFO = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN";
/**
* 获取包含请求参数ACCESS_TOKEN
*/
public static final String ACCESS_TOKEN_WITH_PARAM = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
/**
* 获取小程序 URL Link
*/
public static final String GENERATE_URL_LINK = "https://api.weixin.qq.com/wxa/generate_urllink?access_token=%s";
public static final String WECHATPAY_SIGNATURE_TYPE = "Wechatpay-Signature-Type";
}

View File

@ -0,0 +1,50 @@
package com.ycwl.basic.controller;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.service.FileService;
import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
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.multipart.MultipartFile;
import java.io.IOException;
/**
* @Author: songmingsong
* @CreateTime: 2024-12-05
* @Description: 文件接口
* @Version: 1.0
*/
@RestController
@RequestMapping("/api/file/v1")
@Slf4j
@Api(tags = "文件接口")
public class FileController {
@Autowired
private FileService fileService;
@ApiOperation(value = "上传文件")
@PostMapping("/upload")
@IgnoreToken
public ApiResponse<?> upload(@RequestParam(value = "file") MultipartFile file) throws IOException {
String url = fileService.uploadFile(file);
return ApiResponse.success(url);
}
@ApiOperation(value = "删除文件")
@PostMapping("/delete")
@IgnoreToken
public ApiResponse<?> delete(@RequestParam(value = "fileName") String fileName) throws IOException {
Boolean flag = fileService.delete(fileName);
return flag ? ApiResponse.success(BizCodeEnum.REQUEST_OK) : ApiResponse.fail(BizCodeEnum.FAIL.getMessage());
}
}

View File

@ -14,7 +14,8 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/mobile/face/v1")
@Api(tags = "用户人脸相关接口")
public class AppFaceController {
public class
AppFaceController {
@ApiOperation("人脸有效性校验")

View File

@ -1,12 +1,16 @@
package com.ycwl.basic.controller.mobile;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.model.mobile.DTO.WeChatUserInfoDTO;
import com.ycwl.basic.model.mobile.DTO.WeChatUserInfoUpdateDTO;
import com.ycwl.basic.model.pc.member.resp.MemberRespVO;
import com.ycwl.basic.service.mobile.AppMemberService;
import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* @Authorlongbinbin
@ -15,30 +19,67 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/mobile/member/v1")
@Api(tags = "用户相关接口")
@Slf4j
public class AppMemberController {
@Autowired
private AppMemberService memberService;
/**
* 登录
*
* @param code
* @param userInfoDTO
* @return
* @throws Exception
*/
@ApiOperation("登录")
@PostMapping("/login")
public ApiResponse login() {
//TODO 登录逻辑
return ApiResponse.success("");
}
@PostMapping("/register")
public ApiResponse register() {
//TODO 注册逻辑
return ApiResponse.success("");
@IgnoreToken
public ApiResponse<?> login(@RequestParam(value = "code", required = false) String code,
@RequestParam(value = "userInfoDTO", required = false) WeChatUserInfoDTO userInfoDTO) throws Exception {
return memberService.login(code, userInfoDTO);
}
/**
* 获取用户信息
*
* @return
*/
@ApiOperation("获取用户信息")
@GetMapping("/getUserInfo")
public ApiResponse getUserInfo() {
//TODO 获取用户信息逻辑
return ApiResponse.success("");
public ApiResponse<MemberRespVO> getUserInfo() {
return memberService.getUserInfo();
}
/**
* 修改用户信息
*
* @param userInfoUpdateDTO
* @return
*/
@ApiOperation("修改用户信息")
@PostMapping("/update")
public ApiResponse<?> update(@RequestBody WeChatUserInfoUpdateDTO userInfoUpdateDTO) {
return memberService.update(userInfoUpdateDTO);
}
/**
* 同意用户协议
*
* @return
*/
@ApiOperation("同意用户协议")
@GetMapping("/agreement")
public ApiResponse<?> agreement() {
return memberService.agreement();
}
@ApiOperation("是否首次获取视频")
@GetMapping("/isFirstObtainVideo")
public ApiResponse isFirstTimeObtainingVideo() {
//TODO 判断是否首次获取视频逻辑
// TODO 判断是否首次获取视频逻辑
return ApiResponse.success("");
}

View File

@ -0,0 +1,48 @@
package com.ycwl.basic.controller.mobile;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.model.wxPay.WXPayOrderReqVO;
import com.ycwl.basic.model.wxPay.WxPayRespVO;
import com.ycwl.basic.service.mobile.WxPayService;
import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
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.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @Author: songmingsong
* @CreateTime: 2024-12-05
* @Description: 微信支付
* @Version: 1.0
*/
@RestController
@RequestMapping("/api/mobile/wx/v1")
@Api(tags = "微信支付相关接口")
public class AppWxPayController {
@Autowired
private WxPayService wxPayService;
@ApiOperation(value = "微信预支付", notes = "微信预支付")
@PostMapping("/createOrder")
public ApiResponse<WxPayRespVO> createOrder(@RequestBody @Validated WXPayOrderReqVO req) throws Exception {
return ApiResponse.success(wxPayService.createOrder(req));
}
@ApiOperation(value = "微信支付回调", notes = "微信支付回调")
@PostMapping("/payNotify")
@IgnoreToken
public ApiResponse<?> payNotify(HttpServletRequest request) {
wxPayService.payNotify(request);
return ApiResponse.success(BizCodeEnum.REQUEST_OK);
}
}

View File

@ -0,0 +1,20 @@
package com.ycwl.basic.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* 是否同意用户协议枚举
*
* @author songmingsong
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum AgreementEnum {
AGREE(1, "同意"),
NOT_AGREE(0, "未同意");
private int type;
private String remark;
}

View File

@ -1,36 +0,0 @@
package com.ycwl.basic.enums;
import lombok.Getter;
import lombok.Setter;
/**
* @author songmingsong
* @since 2022-11-23
* 状态码定义
*/
public enum AppStatesCodeEnum {
/**
* 通用操作码
*/
USER_STATES_CODE(1),
UNKNOWN_MISTAKE(500, "未知错误"),
NO_STAFFINFO_ERROR(411, "员工信息不存在"),
;
@Getter
public int code;
@Getter
@Setter
public String message;
AppStatesCodeEnum(int code, String message) {
this.code = code;
this.message = message;
}
AppStatesCodeEnum(Integer statesCode) {
this.code = statesCode;
}
}

View File

@ -1,41 +1,437 @@
package com.ycwl.basic.enums;
import lombok.Getter;
import lombok.Setter;
/**
* @author wenshijia
* @date 2021年05月25日 22:29
* 状态码定义约束共6位数前三位代表服务后4位代表接口
* 比如 商品服务210,购物车是220用户服务230403代表权限
* @author songmingsong
* @since 2022-11-23
* 状态码定义
*/
@Getter
public enum BizCodeEnum {
/* -------------------------------------*/
/* */
/* 0:成功 1:失败 */
/* */
/* -------------------------------------*/
/**
* 通用操作码
* 成功
*/
UNKNOWN_MISTAKE(500, "未知错误"),
SUCCESS(0, "成功"),
VERIFY_CODE_ERROR(600, "验证码错误"),
/**
* 失败
*/
FAIL(1, "失败"),
/**
* 异常
*/
ERROR(-1, "异常"),
NO_VERIFY_CODE_AUTH(700, "未进行授权"),
/* -------------------------------------*/
/* */
/* 200-550 服务器相关 */
/* */
/* -------------------------------------*/
/**
* 无效请求
*/
REQUEST_OK(200, "请求成功"),
/**
* 无效请求
*/
BAD_REQUEST(400, "无效请求"),
TOLL_MACHINE_BIND_APPLICATION_SITE(1000, "当前收费机已经绑定了应用场所"),
TOLL_MACHINE_EXIST(1001, "当前收费机编号已经存在"),
APPLICATION_SITE_EXIST(1002, "当前应用场所已经存在"),
/**
* 参数错误
*/
PARAM_ERROR(400, "参数错误,缺少必要参数"),
CONFERENCE_DEVICE_EXIST(2001, "当前会议设设备已经存在"),
/**
* 无效鉴权信息
*/
UNAUTHORIZED(401, "无效授权信息"),
/**
* 登陆失效
*/
TOKEN_INVALID(401, "登录失效,请重新登录"),
/**
* 无权访问此资源
*/
FORBIDDEN(403, "无权访问此资源"),
/**
* 无效的访问地址
*/
INVALID_URL(404, "无效的访问地址"),
/**
* 不支持的请求方式
*/
NOT_SUPPORTED(405, "不支持的请求方式"),
/**
* 网络异常
*/
REQUEST_TIMEOUT(408, "请求超时"),
/**
* 数据完整性异常 数据过长 过短
*/
LENGTH_REQUIRED(411, "数据完整性异常"),
/**
* 数据约束性异常
*/
CONSTRAINT_EXCEPTION(412, "重复提交或者数据已存在"),
/**
* 数据格式错误
*/
DATA_FORMAT_ERROR(415, "数据格式错误"),
/**
* 此地址暂不可使用
*/
METHOD_FAILURE(420, "此地址暂不可使用"),
/**
* 账户已锁定
*/
LOCKED(423, "账户已锁定,请联系客服"),
/**
* 请求过于频繁
*/
TOO_MANY_REQUESTS(429, "请求过于频繁"),
/**
* 远程访问异常
*/
REMOTE_ACCESS_EXCEPTION(430, " 访问远程主机时发生了异常"),
/**
* 错误的服务API
*/
ERROR_SERVER_API(431, "错误的服务API"),
/**
* 错误的签名
*/
ERROR_SIGN_ACCESS(432, "Access Sign Error"),
/**
* 无效鉴权信息
*/
INVALID_REFRESH(433, "无效刷新信息"),
/**
* 服务器内部错误
*/
SERVER_INTERNAL_ERROR(500, "服务器内部错误"),
/**
* 未知错误
*/
SERVER_UNKONWN_ERROR(500, "未知错误"),
/**
* 暂不可服务
*/
SERVICE_UNAVAILABLE(503, "暂不可服务"),
/**
* 需要认证
*/
AUTHENTICATION_REQUIRED(511, "需要认证"),
/**
* 无数据
*/
NO_DATA(512, "无数据"),
/**
* 存在关联数据
*/
DATA_RELATION(513, "存在关联数据"),
/**
* 该数据已填写
*/
DATA_COMPLETED(514, "该数据已填写"),
/* -------------------------------------*/
/* */
/* 600-619 登录相关业务码 */
/* */
/* -------------------------------------*/
/**
* 账号不存在
*/
ACCOUNT_NON_EXISTENT(601, "账号不存在"),
/**
* 账号已存在
*/
ACCOUNT_EXISTENT(602, "账号已存在"),
/**
* 账户密码错误
*/
ACCOUNT_PASSWORD_ERROR(603, "用户名或密码不正确"),
/**
* 绵阳授权码错误
*/
MYang_AUTHORIZATION_CODE_ERROR(6031, "授权码不正确"),
/**
* 手机号已绑定
*/
TEL_IS_BIND(604, "手机号已绑定"),
/**
* 未绑定手机号
*/
TEL_IS_NOT_BIND(605, "尚未绑定手机号,请先绑定手机号"),
/**
* 无效的登录类型
*/
ERROR_LOGIN_CHANNEL(606, "无效的登录类型"),
/**
* token已过期
*/
TOKEN_EXPIRED(608, "token已过期"),
/**
* 账户在其他地方登录
*/
ACCOUNTS_IS_LOGGED_IN_ELSEWHERE(609, "登录失效,账号已在其他设备登录。"),
/**
* 第三方登录失败
*/
THIRD_LOGIN_ERROR(610, "第三方登录失败"),
/**
* 不支持的登录方式
*/
NOT_SUPPORT_LOGIN_CHANNEL(611, "暂不支持的登录方式"),
/**
* 需要登录后操作
*/
NEED_LOGIN(612, "需要登录后操作"),
/**
* 尚未设置密码
*/
LOGIN_PASSWORD_HAS_NOT_BEEN_SET(613, "账号或密码错误,请重新输入"),
/**
* 注册失败
*/
REGISTER_FAIL(614, "注册失败"),
/**
* 连续登录错误达到阈值请稍后再试
*/
CONTINUOUS_LOGIN_ERROR(615, "错误密码输入次数过多,请%s分钟后再试"),
APP_CONTINUOUS_LOGIN_ERROR(616, "错误密码输入次数过多,请%s分钟后再试"),
/**
* refreshToken已过期
*/
REFRESH_TOKEN_EXPIRE(618, "refreshToken已过期"),
CHANNEL_ERROR(619, "渠道错误"),
/* -------------------------------------*/
/* */
/* 620-639 验证码相关业务码 */
/* */
/* -------------------------------------*/
/**
* 验证码获取失败
*/
VERIFY_CODE_FAIL(621, "验证码获取失败"),
/**
* 无效验证码
*/
INVALID_VERIFY_CODE(622, "无效验证码"),
/**
* 验证码已使用
*/
USED_VERIFY_CODE(623, "验证码已使用"),
/**
* 验证码已过期
*/
EXPIRE_VERIFY_CODE(624, "验证码已失效,请重新输入"),
/**
* 没有输入验证码
*/
NOT_HAS_VERIFY_CODE(625, "没有输入验证码"),
/**
* 错误的验证码
*/
ERROR_VERIFY_CODE(626, "验证码错误,请重新输入"),
/* -------------------------------------*/
/* */
/* 640-659 角色相关业务码 */
/* */
/* -------------------------------------*/
/**
* 角色已存在
*/
ROLE_EXIST(640, "角色已存在"),
/**
* 角色不存在
*/
ROLE_NOT_EXIST(641, "角色不存在"),
HAS_NOT_ROLE(642, "当前登录人没有角色"),
/* -------------------------------------*/
/* */
/* 660-679 文件相关业务码 */
/* */
/* -------------------------------------*/
/**
* 文件过大
*/
FILE_TOO_LARGE(660, "文件过大"),
/**
* 不支持的文件类型
*/
UNSUPPORTED_FILE_TYPE(661, "不支持的文件类型"),
/**
* 文件不存在
*/
FILE_NOT_EXIST(662, "文件不存在"),
/**
* 文件上传失败
*/
UPLOAD_FAILED(663, "文件上传失败"),
/* -------------------------------------*/
/* */
/* 7** 其他业务码 */
/* */
/* -------------------------------------*/
/**
* 开始时间不能大于结束时间
*/
TIME_FAIL(704, "%s开始时间需小于结束时间"),
/**
* 二维码获取失败
*/
QR_CODE_GET_FAIL(705, "二维码获取失败"),
/**
* 有尚未完成的任务
*/
TASK_IS_NOT_OVER(706, "有尚未完成的任务"),
/**
* 该记录已经审核
*/
REMARK_IS_EXAMINE(707, "该记录已经审核"),
/**
* 只有管理员才能操作
*/
SHOP_ONLY_MANAGER_HANDLE(708, "只有管理员才能操作"),
/**
* 群主不能被删除
*/
EMCHAT_GROUP_DELETE_ERROR(709, "群主不能被删除"),
/**
* 存在重复数据
*/
EMCHAT_REPEAT_ERROR(710, "存在重复数据"),
/**
* 姓名或手机号不能为空
*/
NAME_PHONE_NOT_NULL(801, "姓名或手机号不能为空"),
/**
* 该手机号用户已存在
*/
PHONE_EXIST(802, "该手机号用户已存在"),
/**
* 导出失败
*/
EXPORT_FAIL(804, "导出失败"),
/**
* 当前资源路径已被占用
*/
PATH_ALREADY_EXIST(805, "当前资源路径已被占用"),
/**
* 功能不能使用已有的菜单路径
*/
FUNCTION_NOT_MAKE_MENU(806, "功能不能使用已有的菜单路径"),
/**
* 内置角色不能删除
*/
BUILT_IN_ROLE_NOT_DELETE(807, "内置角色不能删除"),
/* -------------------------------------*/
/* */
/* 901-999 角色相关业务码 */
/* */
/* -------------------------------------*/
USER_ORGANIZATION(901, "该手机号不属于有效账号,请重新输入"),
/* -------------------------------------*/
/* */
/* 1000-1019 账号密码相关业务码 */
/* */
/* -------------------------------------*/
OLD_PSW_ERROR(1000, "原密码错误,请重新输入"),
/* -------------------------------------*/
/* */
/* 2000-2019 微信支付相关业务码 */
/* */
/* -------------------------------------*/
ADVANCE_PAYMENT_FAILED(2001, "预支付失败"),
ADVANCE_PAYMENT_CALLBACK_FAILED(2002, "预支付回调失败"),
;
@Getter
public int code;
@Getter
@Setter
public String message;
BizCodeEnum(int code, String message) {
BizCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
private Integer code;
private String message;
}

View File

@ -0,0 +1,25 @@
package com.ycwl.basic.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* 订单状态枚举
*
* @author songmingsong
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum OrderStateEnum {
NOT_PRESENTED(0, "未提出",1),
PASSED(1, "已通过",1),
UNPAID(0, "未支付",2),
PAID(1, "已支付",2),
REFUNDED(2, "已退款",2),
CANCELED(9, "已取消",2);
private int state;
private String remark;
private int type; // 1-退款 2-支付
}

View File

@ -0,0 +1,47 @@
package com.ycwl.basic.enums;
import lombok.Getter;
/**
* <p>@AUTHOR: songmingsong</p>
*/
@Getter
public enum WechatErrorCodeEnum {
/**
* 无效的预登陆code
*/
INVALID_CODE("40029", "无效的预登陆code"),
/**
* 预登陆code已经使用
*/
CODE_IS_USED("40163", "预登陆code已经使用"),
/**
* 无效的AppSecret
*/
INVALID_APP_SECRET("40125", "无效的AppSecret"),
/**
* 未知的微信错误
*/
UNKNOWN_ERROR_CODE("50000", "未知的微信错误");
WechatErrorCodeEnum(String code, String detail) {
this.code = code;
this.detail = detail;
}
private String code;
private String detail;
public static WechatErrorCodeEnum getErrorCode(String code) {
for (WechatErrorCodeEnum wechatErrorCodeEnum : WechatErrorCodeEnum.values()) {
if (wechatErrorCodeEnum.code.equals(code)) {
return wechatErrorCodeEnum;
}
}
return UNKNOWN_ERROR_CODE;
}
}

View File

@ -1,6 +1,6 @@
package com.ycwl.basic.exception;
import com.ycwl.basic.enums.AppStatesCodeEnum;
import com.ycwl.basic.enums.BizCodeEnum;
import lombok.Data;
/**
@ -19,9 +19,14 @@ public class AppException extends RuntimeException {
this.msg = message;
}
public AppException(AppStatesCodeEnum mobileCodeEnum) {
public AppException(BizCodeEnum mobileCodeEnum) {
super(mobileCodeEnum.getMessage());
this.code = mobileCodeEnum.getCode();
this.msg = mobileCodeEnum.getMessage();
}
public AppException(BizCodeEnum mobileCodeEnum, String msg) {
this.code = mobileCodeEnum.getCode();
this.msg = msg;
}
}

View File

@ -59,7 +59,7 @@ public class CustomExceptionHandle {
@ExceptionHandler(value = Exception.class)
public ApiResponse<String> handle(Exception e) {
LOGGER.error("系统异常 -> {}", e.getMessage(), e);
return ApiResponse.buildResult(BizCodeEnum.UNKNOWN_MISTAKE);
return ApiResponse.buildResult(BizCodeEnum.SERVER_UNKONWN_ERROR);
}
/**

View File

@ -39,6 +39,7 @@ public class JwtInfo implements Serializable {
* 用户账号
*/
private String account;
private String phone;

View File

@ -0,0 +1,42 @@
package com.ycwl.basic.model.mobile.DTO;
import lombok.Data;
/**
* @Author: songmingsong
* @CreateTime: 2024-12-05
* @Description: 微信用户信息
* @Version: 1.0
*/
@Data
public class WeChatUserInfoDTO {
/**
* 头像
*/
private String avatarUrl;
/**
* 微信昵称
*/
private String nickname;
/**
* 是否同意用户协议1同意0未同意
*/
private Integer agreement;
/**
* 电话号码
*/
private String phone;
/**
* 国家
*/
private String country;
/**
* 省份
*/
private String province;
/**
* 城市
*/
private String city;
}

View File

@ -0,0 +1,26 @@
package com.ycwl.basic.model.mobile.DTO;
import lombok.Data;
/**
* @Author: songmingsong
* @CreateTime: 2024-12-05
* @Description: 修改微信用户信息
* @Version: 1.0
*/
@Data
public class WeChatUserInfoUpdateDTO {
/**
* 头像
*/
private String avatarUrl;
/**
* 微信昵称
*/
private String nickname;
/**
* 是否同意用户协议1同意0未同意
*/
private Long id;
}

View File

@ -0,0 +1,34 @@
package com.ycwl.basic.model.mobile;
import lombok.Data;
/**
* @Author: songmingsong
* @CreateTime: 2024-12-05
* @Description: 微信用户信息
* @Version: 1.0
*/
@Data
public class WeChatUserInfoModel {
/**
* 微信openid
*/
private String openId;
/**
* 用户id
*/
private String id;
/**
* 头像
*/
private String avatarUrl;
/**
* 微信昵称
*/
private String nickname;
/**
* 真实名称
*/
private String realName;
}

View File

@ -23,6 +23,10 @@ public class MemberEntity {
* 微信昵称
*/
private String nickname;
/**
* 微信头像
*/
private String avatarUrl;
/**
* 真实姓名
*/

View File

@ -0,0 +1,29 @@
package com.ycwl.basic.model.wxPay;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @Author: songmingsong
* @CreateTime: 2024-12-05
* @Description: 预支付请求类
* @Version: 1.0
*/
@Data
@Accessors(chain = true)
public class WXPayOrderReqVO {
@ApiModelProperty(value = "总金额(单位:分)",required = true)
private Integer totalPrice;
@ApiModelProperty(value = "商品名称",required = true)
private String goodsName;
@ApiModelProperty(value = "openid",required = true)
private String openId;
@ApiModelProperty(value = "商品订单号",required = true)
private Long orderSn;
}

View File

@ -0,0 +1,41 @@
package com.ycwl.basic.model.wxPay;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* @Author: songmingsong
* @CreateTime: 2024-12-05
* @Description: 支付返回类
* @Version: 1.0
*/
@Data
@Accessors(chain = true)
public class WxPayRespVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 预支付交易会话标识小程序下单接口返回的prepay_id参数值
*/
@ApiModelProperty("预支付交易会话标识小程序下单接口返回的prepay_id参数值")
private String prepayId;
/**
* 随机字符串
*/
@ApiModelProperty("随机字符串")
private String nonceStr;
/**
* 时间戳
*/
@ApiModelProperty("时间戳")
private Long timeStamp;
/**
* 签名
*/
@ApiModelProperty("签名")
private String paySign;
}

View File

@ -0,0 +1,69 @@
package com.ycwl.basic.model.wxPay;
import cn.hutool.core.date.DateUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.util.Date;
/**
* @Author: songmingsong
* @CreateTime: 2024-12-05
* @Description: 微信订单查询结果
* @Version: 1.0
*/
@Data
@Slf4j
public class WxchatCallbackSuccessData {
/**
* 商户订单号
*/
private String orderId;
/**
* 交易状态
* SUCCESS支付成功
* REFUND转入退款
* NOTPAY未支付
* CLOSED已关闭
* REVOKED已撤销付款码支付
* USERPAYING用户支付中付款码支付
* PAYERROR支付失败(其他原因如银行返回失败)
*/
private String tradestate;
/**
* 支付完成时间
*/
private Date successTime;
/**
* 交易类型
* JSAPI公众号支付
* NATIVE扫码支付
* APPAPP支付
* MICROPAY付款码支付
* MWEBH5支付
* FACEPAY刷脸支付
*/
private String tradetype;
/**
* 订单总金额
*/
private BigDecimal totalMoney;
public Date getSuccessTime() {
return successTime;
}
public void setSuccessTime(String successTime) {
// Hutool工具包的方法自动识别一些常用格式的日期字符串
this.successTime = DateUtil.parse(successTime);
}
}

View File

@ -0,0 +1,29 @@
package com.ycwl.basic.service;
import com.ycwl.basic.utils.OssUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Objects;
/**
* file请求服务
*
* @author songmingsong
*/
@Service
public class FileService {
@Autowired
private OssUtil ossUtil;
public String uploadFile(MultipartFile file) throws IOException {
return ossUtil.uploadFile(file.getInputStream(), Objects.requireNonNull(file.getOriginalFilename()));
}
public Boolean delete(String fileName) {
return ossUtil.deleteFile(fileName);
}
}

View File

@ -0,0 +1,121 @@
package com.ycwl.basic.service;
import com.ycwl.basic.utils.HttpServiceUtil;
import com.ycwl.basic.utils.SslUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Http请求服务
*
* @author songmingsong
*/
@Service
@Slf4j
public class HttpService {
/**
* @param requestUrl
* 请求地址
* @param params
* 参数
* @param encoding
* 编码
* @return String
* @throws Exception
* 抛出的异常新
*/
public String doHttpsPost(String requestUrl, Map<String, String> params, String encoding) throws Exception {
String result = HttpServiceUtil.REQUEST_NO_RESULT;
// build client 对象
CloseableHttpClient httpClient = null;
try {
httpClient = SslUtil.sslHttpClientBuild();
requestUrl = requestUrl.replaceAll("\\s*", "");
HttpPost post = new HttpPost(requestUrl);
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(5000)
.setConnectionRequestTimeout(1000).setSocketTimeout(5000).build();
post.setConfig(requestConfig);
// 请求首部--可选的User-Agent对于一些服务器必选不加可能不会返回正确结果
post.setHeader("User-Agent",
"Mozilla/5.0 (Linux; U; Android 5.0.2; zh-cn; PLK-UL00 Build/HONORPLK-UL00) AppleWebKit/533.1 (KHTML, like Gecko)Version/4.0 MQQBrowser/5.4 TBS/025483 Mobile Safari/533.1 MicroMessenger/6.3.8.56_re6b2553.680 NetType/");
List<NameValuePair> parasList = new ArrayList<NameValuePair>();
if (null != params && params.size() > 0) {
for (String key : params.keySet()) {
NameValuePair param = new BasicNameValuePair(key, params.get(key));
parasList.add(param);
}
}
// Exec Request
HttpEntity paramsEntity = new UrlEncodedFormEntity(parasList, encoding);
post.setEntity(paramsEntity);
CloseableHttpResponse resp = httpClient.execute(post);
// 获得起始行
StatusLine status = resp.getStatusLine();
// 获取实体
if (HttpServiceUtil.success(status)) {
// 获取实体
HttpEntity entity = resp.getEntity();
// 编码格式
result = EntityUtils.toString(entity, encoding);
// 释放实体 response.close();//关闭响应
EntityUtils.consume(entity);
}
} catch (Exception e) {
log.error("doHttpsPost", e);
throw e;
} finally {
if (httpClient != null) {
httpClient.close();
}
}
return result;
}
public String doGet(String requestUrl, String encoding) throws Exception {
String result = HttpServiceUtil.REQUEST_NO_RESULT;
// build client 对象
CloseableHttpClient httpClient = null;
try {
httpClient = SslUtil.sslHttpClientBuild();
requestUrl = requestUrl.replaceAll("\\s*", "");
HttpGet get = new HttpGet(requestUrl);
// 请求首部--可选的User-Agent对于一些服务器必选不加可能不会返回正确结果
get.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64;rv:39.0) Gecko/20100101 Firefox/39.0");
// Exec Request
CloseableHttpResponse resp = httpClient.execute(get);
// 获得起始行
StatusLine status = resp.getStatusLine();
if (HttpServiceUtil.success(status)) {
// 获取实体
HttpEntity entity = resp.getEntity();
// 编码格式
result = EntityUtils.toString(entity, encoding);
// 释放实体 response.close();//关闭响应
EntityUtils.consume(entity);
}
} finally {
if (httpClient != null) {
httpClient.close();
}
}
return result;
}
}

View File

@ -0,0 +1,141 @@
package com.ycwl.basic.service.impl.mobile;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ycwl.basic.config.WechatConfig;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.constant.NumberConstant;
import com.ycwl.basic.constant.WeiXinConstant;
import com.ycwl.basic.enums.AgreementEnum;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.enums.WechatErrorCodeEnum;
import com.ycwl.basic.exception.AppException;
import com.ycwl.basic.mapper.pc.MemberMapper;
import com.ycwl.basic.model.jwt.JwtInfo;
import com.ycwl.basic.model.mobile.DTO.WeChatUserInfoDTO;
import com.ycwl.basic.model.mobile.DTO.WeChatUserInfoUpdateDTO;
import com.ycwl.basic.model.pc.member.entity.MemberEntity;
import com.ycwl.basic.model.pc.member.req.MemberReqQuery;
import com.ycwl.basic.model.pc.member.resp.MemberRespVO;
import com.ycwl.basic.service.HttpService;
import com.ycwl.basic.service.mobile.AppMemberService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.BeanCopierUtils;
import com.ycwl.basic.utils.JwtTokenUtil;
import com.ycwl.basic.utils.SnowFlakeUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Authorsongmingsong
*/
@Slf4j
@Service
public class AppMemberServiceImpl implements AppMemberService {
@Autowired
private WechatConfig config;
@Autowired
private HttpService httpService;
@Autowired
private MemberMapper memberMapper;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
public Map<String, Object> getOpenId(String code) {
Map<String, String> paramMap = new HashMap<>(NumberConstant.FOUR);
paramMap.put("appid", config.getMiniProgramAppId());
paramMap.put("secret", config.getMiniProgramSecret());
paramMap.put("js_code", code);
paramMap.put("grant_type", config.getGrandType());
try {
String response = httpService.doHttpsPost(WeiXinConstant.GET_OPEN_ID, paramMap, "UTF-8");
if (StringUtils.isBlank(response)) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(response, Map.class);
} catch (Exception e) {
log.error("getOpenId", e);
throw new AppException(BizCodeEnum.SERVER_INTERNAL_ERROR);
}
}
@Override
public ApiResponse login(String code, WeChatUserInfoDTO userInfoDTO) throws Exception {
Map<String, Object> weixinResponse = this.getOpenId(code);
if (CollectionUtils.isEmpty(weixinResponse)) {
throw new AppException(BizCodeEnum.SERVER_INTERNAL_ERROR);
}
Object errcode = weixinResponse.get("errcode");
if (errcode != null) {
WechatErrorCodeEnum errorCode = WechatErrorCodeEnum.getErrorCode(errcode.toString());
throw new AppException(BizCodeEnum.PARAM_ERROR.getCode(), errorCode.getDetail());
}
// 获取openId
Object openId = weixinResponse.get("openid");
if (openId == null) {
log.warn("warning={}", "未获取到当前用户openId");
throw new AppException(BizCodeEnum.SERVER_UNKONWN_ERROR, "未获取到当前用户openId");
}
MemberRespVO memberRespVO = new MemberRespVO();
JwtInfo jwtInfo = new JwtInfo();
// 根据返回的openId判断用户是否是新用户是的话将用户信息存到数据库
MemberReqQuery memberReqQuery = new MemberReqQuery();
memberReqQuery.setOpenId(openId.toString());
List<MemberRespVO> list = memberMapper.list(memberReqQuery);
if (list.isEmpty()) {
MemberEntity memberEntity = new MemberEntity();
memberEntity.setId(SnowFlakeUtil.getLongId());
BeanCopierUtils.copyProperties(userInfoDTO, memberEntity);
memberMapper.add(memberEntity);
BeanCopierUtils.copyProperties(memberEntity, memberRespVO);
} else {
BeanCopierUtils.copyProperties(list.get(NumberConstant.ZERO), memberRespVO);
}
jwtInfo.setUserId(memberRespVO.getId().toString());
jwtInfo.setName(memberRespVO.getNickname());
jwtInfo.setPhone(memberRespVO.getPhone());
String jwt = jwtTokenUtil.generateToken(jwtInfo);
Map<String, Object> resMap = new HashMap<>();
resMap.put("token", jwt);
resMap.put("user", memberRespVO);
return ApiResponse.success(resMap);
}
@Override
public ApiResponse<MemberRespVO> getUserInfo() {
MemberRespVO respVO = memberMapper.getById(Long.parseLong(BaseContextHandler.getUserId()));
return ApiResponse.success(respVO);
}
@Override
public ApiResponse<?> update(WeChatUserInfoUpdateDTO userInfoUpdateDTO) {
MemberEntity memberEntity = new MemberEntity();
memberEntity.setId(Long.parseLong(BaseContextHandler.getUserId()));
BeanCopierUtils.copyProperties(userInfoUpdateDTO, memberEntity);
return ApiResponse.success(memberMapper.update(memberEntity));
}
@Override
public ApiResponse<?> agreement() {
MemberEntity memberEntity = new MemberEntity();
memberEntity.setId(Long.parseLong(BaseContextHandler.getUserId()));
memberEntity.setAgreement(AgreementEnum.AGREE.getType());
return ApiResponse.success(memberMapper.update(memberEntity));
}
}

View File

@ -0,0 +1,201 @@
package com.ycwl.basic.service.impl.mobile;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.TransactionAmount;
import com.ycwl.basic.config.WechatConfig;
import com.ycwl.basic.constant.WeiXinConstant;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.enums.OrderStateEnum;
import com.ycwl.basic.exception.AppException;
import com.ycwl.basic.mapper.pc.OrderMapper;
import com.ycwl.basic.model.wxPay.WXPayOrderReqVO;
import com.ycwl.basic.model.wxPay.WxPayRespVO;
import com.ycwl.basic.model.wxPay.WxchatCallbackSuccessData;
import com.ycwl.basic.service.mobile.WxPayService;
import com.ycwl.basic.service.pc.OrderService;
import com.ycwl.basic.utils.WXPayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.wechat.pay.java.core.http.Constant.*;
/**
* @Author: songmingsong
* @CreateTime: 2024-12-05
* @Description: 微信支付实现
* @Version: 1.0
*/
@Slf4j
@Service
public class WxPayServiceImpl implements WxPayService {
@Autowired
private WechatConfig wechatConfig;
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderService orderService;
@Override
public WxPayRespVO createOrder(WXPayOrderReqVO req) {
try {
// 使用自动更新平台证书的RSA配置
// 一个商户号只能初始化一个配置否则会因为重复的下载任务报错
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(wechatConfig.getMchId())
.privateKeyFromPath(wechatConfig.getKeyPath())
.merchantSerialNumber(wechatConfig.getMchSerialNo())
.apiV3Key(wechatConfig.getApiV3())
.build();
// 构建service
JsapiService service = new JsapiService.Builder().config(config).build();
// request.setXxx(val)设置所需参数具体参数可见Request定义
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(req.getTotalPrice());
request.setAmount(amount);
request.setAppid(wechatConfig.getAppId());
request.setMchid(wechatConfig.getMchId());
request.setDescription(req.getGoodsName());
request.setNotifyUrl(wechatConfig.getNotifyUrl());
request.setOutTradeNo(req.getOrderSn().toString());
Payer payer = new Payer();
payer.setOpenid(req.getOpenId());
request.setPayer(payer);
// 调用下单方法得到应答
PrepayResponse response = service.prepay(request);
WxPayRespVO vo = new WxPayRespVO();
Long timeStamp = System.currentTimeMillis() / 1000;
vo.setTimeStamp(timeStamp);
String substring = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
vo.setNonceStr(substring);
String signatureStr = Stream.of(wechatConfig.getAppId(), String.valueOf(timeStamp), substring, "prepay_id=" + response.getPrepayId())
.collect(Collectors.joining("\n", "", "\n"));
String sign = WXPayUtil.getSign(signatureStr, wechatConfig.getKeyPath());
vo.setPaySign(sign);
vo.setPrepayId("prepay_id=" + response.getPrepayId());
return vo;
} catch (ServiceException e) {
JSONObject parse = JSONObject.parseObject(e.getResponseBody());
throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_FAILED, parse.getString("message"));
} catch (Exception e) {
throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_FAILED, e.toString());
}
}
@Override
public void payNotify(HttpServletRequest request) {
try {
// 读取请求体的信息
ServletInputStream inputStream = request.getInputStream();
StringBuffer stringBuffer = new StringBuffer();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s;
// 读取回调请求体
while ((s = bufferedReader.readLine()) != null) {
stringBuffer.append(s);
}
String s1 = stringBuffer.toString();
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
String signType = request.getHeader(WeiXinConstant.WECHATPAY_SIGNATURE_TYPE);
String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
NotificationConfig config = new RSAAutoCertificateConfig.Builder()
.merchantId(wechatConfig.getMchId())
.privateKeyFromPath(wechatConfig.getKeyPath())
.merchantSerialNumber(wechatConfig.getMchSerialNo())
.apiV3Key(wechatConfig.getApiV3())
.build();
// 初始化 NotificationParser
NotificationParser parser = new NotificationParser(config);
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serialNo)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
// 若未设置signType默认值为 WECHATPAY2-SHA256-RSA2048
.signType(signType)
.body(s1)
.build();
Transaction parse = parser.parse(requestParam, Transaction.class);
log.info("[微信支付]parse = {}", parse);
// 更新订单信息
new Thread(() -> {
Transaction.TradeStateEnum tradeState = parse.getTradeState();
OrderStateEnum OrderStateEnum = null;
if (Transaction.TradeStateEnum.SUCCESS == tradeState) {
OrderStateEnum = OrderStateEnum.PAID;
}
if (Objects.nonNull(OrderStateEnum)) {
orderService.updateOrderState(Long.parseLong(parse.getOutTradeNo()), OrderStateEnum, null);
}
}).start();
} catch (Exception e) {
throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_CALLBACK_FAILED, e.toString());
}
}
@Override
public WxchatCallbackSuccessData queryPay(Long orderId) {
WxchatCallbackSuccessData wxchatCallbackSuccessData = new WxchatCallbackSuccessData();
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
queryRequest.setMchid(wechatConfig.getMchId());
queryRequest.setOutTradeNo(orderId.toString());
// 一个商户号只能初始化一个配置否则会因为重复的下载任务报错
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(wechatConfig.getMchId())
.privateKeyFromPath(wechatConfig.getKeyPath())
.merchantSerialNumber(wechatConfig.getMchSerialNo())
.apiV3Key(wechatConfig.getApiV3())
.build();
// 构建service
JsapiService service = new JsapiService.Builder().config(config).build();
try {
com.wechat.pay.java.service.payments.model.Transaction transaction = service.queryOrderByOutTradeNo(queryRequest);
String successTime = transaction.getSuccessTime();
wxchatCallbackSuccessData.setOrderId(orderId.toString());
wxchatCallbackSuccessData.setSuccessTime(successTime);
wxchatCallbackSuccessData.setTradetype(WeiXinConstant.getDescriptionType(transaction.getTradeType()));
wxchatCallbackSuccessData.setTradestate(WeiXinConstant.getDescriptionState(transaction.getTradeState()));
TransactionAmount amount = transaction.getAmount();
Integer total = amount.getTotal();
wxchatCallbackSuccessData.setTotalMoney(new BigDecimal(total).movePointLeft(2));
} catch (ServiceException e) {
// API返回失败, 例如ORDER_NOT_EXISTS
log.error("code={}, message={}", e.getErrorCode(), e.getErrorMessage());
log.error("reponse body={}", e.getResponseBody());
}
return wxchatCallbackSuccessData;
}
}

View File

@ -3,6 +3,8 @@ package com.ycwl.basic.service.impl.pc;
import cn.hutool.core.bean.BeanUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.constant.NumberConstant;
import com.ycwl.basic.enums.OrderStateEnum;
import com.ycwl.basic.mapper.pc.OrderMapper;
import com.ycwl.basic.model.pc.order.entity.OrderItemEntity;
import com.ycwl.basic.model.pc.order.req.OrderAddOrUpdateReq;
@ -18,6 +20,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
@ -82,4 +85,28 @@ public class OrderServiceImpl implements OrderService {
}
return ApiResponse.success(update);
}
/**
* 更新订单支付状态
*
* @param orderSn 订单编号也就是订单的ID
*/
@Override
public void updateOrderState(Long orderSn, OrderStateEnum orderStateEnum, String refundReason) {
OrderAddOrUpdateReq orderAddOrUpdateReq = new OrderAddOrUpdateReq();
orderAddOrUpdateReq.setId(orderSn);
if (orderStateEnum.getType() == NumberConstant.ONE) {
orderAddOrUpdateReq.setRefundStatus(orderStateEnum.getState());
orderAddOrUpdateReq.setRefundAt(new Date());
orderAddOrUpdateReq.setRefundReason(refundReason);
} else if (orderStateEnum.getType() == NumberConstant.TWO) {
int state = orderStateEnum.getState();
orderAddOrUpdateReq.setPayAt(new Date());
orderAddOrUpdateReq.setStatus(orderStateEnum.getState());
if (state == OrderStateEnum.CANCELED.getState()) {
orderAddOrUpdateReq.setCancelAt(new Date());
}
}
orderMapper.update(orderAddOrUpdateReq);
}
}

View File

@ -0,0 +1,52 @@
package com.ycwl.basic.service.mobile;
import com.ycwl.basic.model.mobile.DTO.WeChatUserInfoDTO;
import com.ycwl.basic.model.mobile.DTO.WeChatUserInfoUpdateDTO;
import com.ycwl.basic.model.pc.member.resp.MemberRespVO;
import com.ycwl.basic.utils.ApiResponse;
import java.util.Map;
/**
* @Authorsongmingsong
*/
public interface AppMemberService {
/**
* 获取用户的openId
*
* @return Map
*/
Map<String, Object> getOpenId(String code);
/**
* 登录
*
* @param code 前端授权码
* @param userInfoDTO 实体信息
* @return
*/
ApiResponse login(String code, WeChatUserInfoDTO userInfoDTO) throws Exception;
/**
* 获取用户信息
*
* @return
*/
ApiResponse<MemberRespVO> getUserInfo();
/**
* 修改信息
*
* @param userInfoUpdateDTO
* @return
*/
ApiResponse<?> update(WeChatUserInfoUpdateDTO userInfoUpdateDTO);
/**
* 同意用户协议
*
* @return
*/
ApiResponse<?> agreement();
}

View File

@ -0,0 +1,25 @@
package com.ycwl.basic.service.mobile;
import com.ycwl.basic.model.wxPay.WXPayOrderReqVO;
import com.ycwl.basic.model.wxPay.WxPayRespVO;
import com.ycwl.basic.model.wxPay.WxchatCallbackSuccessData;
import javax.servlet.http.HttpServletRequest;
public interface WxPayService {
/**
* 微信预支付
*/
WxPayRespVO createOrder(WXPayOrderReqVO req) throws Exception;
/**
* 微信支付回调
*/
void payNotify(HttpServletRequest request);
/**
* 微信支付结果查询
*/
WxchatCallbackSuccessData queryPay(Long orderId);
}

View File

@ -1,7 +1,7 @@
package com.ycwl.basic.service.pc;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.model.pc.order.entity.OrderEntity;
import com.ycwl.basic.enums.OrderStateEnum;
import com.ycwl.basic.model.pc.order.req.OrderAddOrUpdateReq;
import com.ycwl.basic.model.pc.order.req.OrderReqQuery;
import com.ycwl.basic.model.pc.order.resp.OrderRespVO;
@ -19,4 +19,7 @@ public interface OrderService {
ApiResponse<OrderRespVO> detail(Long orderId);
ApiResponse<Integer> add(OrderAddOrUpdateReq query);
ApiResponse<Integer> update(OrderAddOrUpdateReq query);
void updateOrderState(Long orderSn, OrderStateEnum orderStateEnum, String refundReason);
}

View File

@ -142,8 +142,8 @@ public class ApiResponse<T> implements Serializable {
public static <T> ApiResponse<T> buildResult(BizCodeEnum bizCodeEnum) {
ApiResponse<T> apiResponse = new ApiResponse();
apiResponse.setCode(bizCodeEnum.code);
apiResponse.setMsg(bizCodeEnum.message);
apiResponse.setCode(bizCodeEnum.getCode());
apiResponse.setMsg(bizCodeEnum.getMessage());
return apiResponse;
}

View File

@ -0,0 +1,31 @@
package com.ycwl.basic.utils;
import org.apache.http.StatusLine;
/**
* Http服务工具类
* @author songmingsong
*/
public class HttpServiceUtil {
/**
* 请求OK代码
*/
public static final int REQUEST_OK_CODE=200;
/**
* 无响应内容
*/
public static final String REQUEST_NO_RESULT="No_Result";
/**
* 是否响应成功
* @param status 响应状态
* @return boolean
*/
public static boolean success(StatusLine status) {
return status.getStatusCode() == REQUEST_OK_CODE;
}
}

View File

@ -60,55 +60,4 @@ public class JwtTokenUtil {
throw new CheckTokenException("token is invalid");
}
}
/*********************************************** 测试 ***************************************************/
/**
* 测试 token
*
* @param args
*/
// public static void main(String[] args) throws Exception {
//
// JwtInfo jwtInfo = new JwtInfo("阿豹", "1", "role1", "yangchen", 1, LocalDateTime.now(), new HashMap<>());
//
// long a = Instant.now().toEpochMilli();
//
// JwtTokenUtil jwtTokenUtil = new JwtTokenUtil();
// String token = jwtTokenUtil.generateToken(jwtInfo);
//
// log.info("==> generate token: " + (Instant.now().toEpochMilli() - a) + " ms");
//
// System.out.println("=======");
// System.out.println();
//
// System.out.println(token);
//
// System.out.println();
// System.out.println("=======");
//
// long b = Instant.now().toEpochMilli();
//
// JwtInfo jwtInfo1 = jwtTokenUtil.parsingToken(token);
//
// log.info("==> paring token end " + (Instant.now().toEpochMilli() - b) + " ms");
//
// System.out.println();
// System.out.println();
// System.out.println();
// System.out.println(jwtInfo);
// System.out.println("=======");
// System.out.println(jwtInfo1.toString());
//
// }
public static void main(String[] args) throws Exception {
JwtInfo jwtInfo = new JwtInfo();
jwtInfo.setUserId("1");
LocalDateTime expireTime = LocalDateTime.now().plusDays(9999999);
byte[] bytes = RsaKeyUtil.toBytes(PRI_KEY);
String token = JwtAnalysisUtil.generateToken(jwtInfo, bytes, expireTime);
System.out.println(token);
}
}

View File

@ -0,0 +1,85 @@
package com.ycwl.basic.utils;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;
import com.ycwl.basic.config.OssConfig;
import com.ycwl.basic.enums.BizCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.InputStream;
@Slf4j
@Component
public class OssUtil {
@Autowired
private OssConfig ossConfig;
public String uploadFile(InputStream inputStream, String filename) {
String uploadFileName = ossConfig.getObjectName() + System.currentTimeMillis() + filename.substring(filename.lastIndexOf("."));
OSS ossClient = new OSSClientBuilder().build(ossConfig.getEndPoint(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());
try {
PutObjectRequest putObjectRequest = new PutObjectRequest(ossConfig.getBucketName(), uploadFileName, inputStream);
ossClient.putObject(putObjectRequest);
String fileUrl = ossConfig.getUrl() + uploadFileName;
return fileUrl;
} catch (OSSException oe) {
log.error("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason."
+ " \n Error Message:" + oe.getErrorMessage()
+ " \n Error Code:" + oe.getErrorCode()
+ " \n Request ID:" + oe.getRequestId()
+ " \n Host ID:" + oe.getHostId()
);
} catch (ClientException ce) {
log.error("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network."
+ "Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
return BizCodeEnum.UPLOAD_FAILED.getMessage();
}
public boolean deleteFile(String filename) {
// 填写文件完整路径文件完整路径中不能包含Bucket名称
String objectName = filename;
OSS ossClient = new OSSClientBuilder().build(ossConfig.getEndPoint(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());
try {
// 删除文件或目录如果要删除目录目录必须为空
ossClient.deleteObject(ossConfig.getBucketName(), objectName);
return true;
} catch (OSSException oe) {
log.error("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason."
+ " \n Error Message:" + oe.getErrorMessage()
+ " \n Error Code:" + oe.getErrorCode()
+ " \n Request ID:" + oe.getRequestId()
+ " \n Host ID:" + oe.getHostId()
);
} catch (ClientException ce) {
log.error("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network."
+ "Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
return false;
}
}

View File

@ -0,0 +1,98 @@
package com.ycwl.basic.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/**
* SSL工具类
*
* @author songmingsong
*/
@Slf4j
public class SslUtil {
/**
* 获取HtttpClient对象
*
* @return CloseableHttpClient
*/
public static CloseableHttpClient sslHttpClientBuild() {
Registry<ConnectionSocketFactory> socketFactoryRegistry =
RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", trustAllHttpsCertificates()).build();
// 创建ConnectionManager添加Connection配置信息
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager(socketFactoryRegistry);
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();
return httpClient;
}
/**
* 信任所有Http证书
*
* @return SSLConnectionSocketFactory
*/
private static SSLConnectionSocketFactory trustAllHttpsCertificates() {
SSLConnectionSocketFactory socketFactory = null;
TrustManager[] trustAllCerts = new TrustManager[1];
TrustManager tm = new X509TrustManager() {
@Override
// 返回受信任的X509证书数组
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
// 该方法检查服务器的证书若不信任该证书同样抛出异常通过自己实现该方法可以使之信任我们指定的任何证书
// 在实现该方法时也可以简单的不做任何处理即一个空的函数体由于不会抛出异常它就会信任任何证书
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
if (chain!=null&&chain.length>0) {
chain[0].checkValidity();
}
} catch (Exception e) {
log.error("checkServerTrusted",e);
}
}
@Override
// 该方法检查客户端的证书若不信任该证书则抛出异常由于我们不需要对客户端进行认证
// 因此我们只需要执行默认的信任管理器的这个方法JSSE中默认的信任管理器类为TrustManager
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
if (chain!=null&&chain.length>0) {
chain[0].checkValidity();
}
} catch (Exception e) {
log.error("checkClientTrusted",e);
}
}
};
trustAllCerts[0] = tm;
SSLContext sc = null;
try {
sc = SSLContext.getInstance("TLSv1.2");
sc.init(null, trustAllCerts, null);
socketFactory = new SSLConnectionSocketFactory(sc, NoopHostnameVerifier.INSTANCE);
// HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
log.error("trustAllHttpsCertificates", e);
}
return socketFactory;
}
}

View File

@ -0,0 +1,49 @@
package com.ycwl.basic.utils;
import com.wechat.pay.java.core.util.PemUtil;
import org.springframework.util.Base64Utils;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Random;
/**
* @Author: songmingsong
* @CreateTime: 2024-12-05
* @Description: 微信支付
* @Version: 1.0
*/
public class WXPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
public static String getSign(String signatureStr,String privateKey) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException, URISyntaxException {
//replace 根据实际情况不一定都需要
String replace = privateKey.replace("\\n", "\n");
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKeyFromPath(replace);
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(merchantPrivateKey);
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return Base64Utils.encodeToString(sign.sign());
}
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
char[] nonceChars = new char[32];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
}

View File

@ -11,7 +11,7 @@ spring:
hiddenmethod:
filter:
enabled: true
datasource: # 数据源的相关配置
datasource: # 数据源的相关配置
type: com.zaxxer.hikari.HikariDataSource # 数据源类型HikariCP
driver-class-name: com.mysql.cj.jdbc.Driver # mysql驱动
url: jdbc:mysql://8.134.112.96:3306/liuying_mgmt_re?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
@ -60,3 +60,38 @@ mybatis-plus:
# 指定使用的日志配置文件
logging:
config: classpath:logback-spring.xml
# 微信小程序相关配置
wx:
# 公众号的appId
appId: fix
# 公众号的密钥
appSecret: fix
# 小程序的AppId
miniProgramAppId: 1
# 小程序的secret
miniProgramSecret: 1
# 申请openid授权
grandType: authorization_code
# 推送模板
push:
templateId: 1
# 商户号
mchId: xxxx
# 商户证书序列号
mchSerialNo: xxxxx
# 回调接口地址
notifyUrl: https://xxxx/a/biz/wxpay/payNotify
# 商户API私钥路径
keyPath: module-app/src/main/resources/cert/apiclient_key.pem
# 商户APIV3密钥
apiV3: 1
#阿里云OSS
aliYunOss:
endpoint: "https://oss-cn-chengdu.aliyuncs.com"
accessKeyId: "LTAI5t5cydpGpHfYqf31mEJA"
accessKeySecret: "xdFzlwzmE8QPPstbcZY82tU3xj7G0R"
bucketName: "scwzzn-file"
objectName: "image/"
url: "https://scwzzn-file.oss-cn-chengdu.aliyuncs.com/"
region: "cn-hangzhou"