v3.0.0 springboot3+jdk17版本

This commit is contained in:
hupeng committed 2024-06-01 16:29:28 +08:00
1 parent 3aa2c942dc
commit b3ee447f8e
3676 files changed
+64366 -370384

No files matched your search

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>co.yixiang.boot</groupId>
<artifactId>yshop</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<modules>
<module>yshop-module-member-api</module>
<module>yshop-module-member-biz</module>
</modules>
<artifactId>yshop-module-member</artifactId>
<packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>
member 模块,我们放会员业务。
例如说:会员中心等等
</description>
</project>
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>co.yixiang.boot</groupId>
<artifactId>yshop-module-member</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yshop-module-member-api</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
member 模块 API,暴露给其它模块调用
</description>
<dependencies>
<dependency>
<groupId>co.yixiang.boot</groupId>
<artifactId>yshop-common</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,4 @@
/**
* member API 包,定义暴露给其它模块的 API
*/
package co.yixiang.yshop.module.member.api;
@@ -0,0 +1,63 @@
package co.yixiang.yshop.module.member.api.user;
import co.yixiang.yshop.module.member.api.user.dto.MemberUserRespDTO;
import co.yixiang.yshop.module.member.api.user.dto.WechatUserDto;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertMap;
/**
* 会员用户的 API 接口
*
* @author yshop
*/
public interface MemberUserApi {
void saveWechatMember(WechatUserDto wechatUserDto);
/**
* 获得会员用户信息
*
* @param id 用户编号
* @return 用户信息
*/
MemberUserRespDTO getUser(Long id);
/**
* 获得会员用户信息们
*
* @param ids 用户编号的数组
* @return 用户信息们
*/
List<MemberUserRespDTO> getUsers(Collection<Long> ids);
/**
* 获得会员用户 Map
*
* @param ids 用户编号的数组
* @return 会员用户 Map
*/
default Map<Long, MemberUserRespDTO> getUserMap(Collection<Long> ids) {
return convertMap(getUsers(ids), MemberUserRespDTO::getId);
}
/**
* 基于用户昵称,模糊匹配用户列表
*
* @param nickname 用户昵称,模糊匹配
* @return 用户信息的列表
*/
List<MemberUserRespDTO> getUserListByNickname(String nickname);
/**
* 基于手机号,精准匹配用户
*
* @param mobile 手机号
* @return 用户信息
*/
MemberUserRespDTO getUserByMobile(String mobile);
}
@@ -0,0 +1,34 @@
package co.yixiang.yshop.module.member.api.user.dto;
import co.yixiang.yshop.framework.common.enums.CommonStatusEnum;
import lombok.Data;
/**
* 用户信息 Response DTO
*
* @author yshop
*/
@Data
public class MemberUserRespDTO {
/**
* 用户ID
*/
private Long id;
/**
* 用户昵称
*/
private String nickname;
/**
* 帐号状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 手机
*/
private String mobile;
}
@@ -0,0 +1,41 @@
package co.yixiang.yshop.module.member.api.user.dto;
import lombok.*;
/**
* @ClassName WechatUserDTO
* @Author hupeng <610796224@qq.com>
* @Date 2023/7/18
**/
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WechatUserDto {
private String openid;
private String unionId;
private String routineOpenid;
private String nickname;
private String headimgurl;
private String sex;
private String city;
private String language;
private String province;
private String country;
private Boolean subscribe;
private Long subscribeTime;
}
@@ -0,0 +1,40 @@
/**
* Copyright (C) 2018-2022
* All rights reserved, Designed By www.yixiang.co
*/
package co.yixiang.yshop.module.member.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author hupeng
* 账单明细相关枚举
*/
@Getter
@AllArgsConstructor
public enum BillDetailEnum {
TYPE_1("recharge","充值"),
TYPE_2("brokerage","返佣"),
TYPE_3("pay_product","消费"),
TYPE_4("extract","提现"),
TYPE_5("pay_product_refund","退款"),
TYPE_6("system_add","系统添加"),
TYPE_7("system_sub","系统减少"),
TYPE_8("deduction","减去"),
TYPE_9("gain","奖励"),
TYPE_10("sign","签到"),
CATEGORY_1("now_money","金额"),
CATEGORY_2("integral","积分");
private String value;
private String desc;
}
@@ -0,0 +1,44 @@
/**
* Copyright (C) 2018-2022
* All rights reserved, Designed By www.yixiang.co
*/
package co.yixiang.yshop.module.member.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.stream.Stream;
/**
* @author hupeng
* 账单相关枚举
*/
@Getter
@AllArgsConstructor
public enum BillEnum {
PM_0(0,"支出"),
PM_1(1,"获得"),
STATUS_0(0,"默认"),
STATUS_1(1,"有效"),
STATUS_2(2,"无效");
private Integer value;
private String desc;
public static BillEnum toType(int value) {
return Stream.of(BillEnum.values())
.filter(p -> p.value == value)
.findAny()
.orElse(null);
}
}
@@ -0,0 +1,31 @@
package co.yixiang.yshop.module.member.enums;
import co.yixiang.yshop.framework.common.exception.ErrorCode;
/**
* Member 错误码枚举类
*
* member 系统,使用 1-004-000-000 段
*/
public interface ErrorCodeConstants {
// ========== 用户相关 1004001000============
ErrorCode USER_NOT_EXISTS = new ErrorCode(1004001000, "用户不存在");
ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1004001001, "密码校验失败");
// ========== AUTH 模块 1004003000 ==========
ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确");
ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1004003001, "登录失败,账号被禁用");
ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1004003004, "Token 已经过期");
ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1004003005, "未绑定账号,需要进行绑定");
ErrorCode AUTH_WEIXIN_MINI_APP_PHONE_CODE_ERROR = new ErrorCode(1004003006, "获得手机号失败");
// ========== 用户收件地址 1004004000 ==========
ErrorCode USER_ADDRESS_NOT_EXISTS = new ErrorCode(1004004000, "用户收件地址不存在");
ErrorCode USER_ADDRESS_PARAM_NOT_EXISTS = new ErrorCode(1004004001, "用户收件地址参数错误");
ErrorCode USER_BILL_NOT_EXISTS = new ErrorCode(1004004001, "用户账单不存在");
ErrorCode MINI_AUTH_LOGIN_BAD = new ErrorCode(1004004002, "登录失败,请联系管理员");
ErrorCode COUPON_NOT_CONDITION = new ErrorCode(1004004003, "此优惠券不满足使用提交");
ErrorCode MINI_AUTH_LOGIN_BAD2 = new ErrorCode(1004004002, "登录失败,请返回首页刷新重新登录");
}
@@ -0,0 +1,41 @@
/**
* Copyright (C) 2018-2022
* All rights reserved, Designed By www.yixiang.co
*/
package co.yixiang.yshop.module.member.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author hupeng
* 账单明细相关枚举
*/
@Getter
@AllArgsConstructor
public enum LoginTypeEnum {
WEIXIN_H5("weixinh5","weixinh5"),
H5("h5","H5"),
WECHAT("wechat","公众号"),
APP("app","APP"),
PC("pc","PC"),
ROUNTINE("routine","小程序"),
UNIAPPH5("uniappH5","uniappH5");
//
// WXAPP("wxapp","微信小程序"),
// ALIAPP("aliapp","支付宝小程序"),
// WECHAT("wechat","微信公众号"),
// H5("h5","h5"),
// PC("pc","pc"),
// APP("app","APP");
private String value;
private String desc;
}
@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>co.yixiang.boot</groupId>
<artifactId>yshop-module-member</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yshop-module-member-biz</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
member 模块,我们放会员业务。
例如说:会员中心等等
</description>
<dependencies>
<dependency>
<groupId>co.yixiang.boot</groupId>
<artifactId>yshop-module-member-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>co.yixiang.boot</groupId>
<artifactId>yshop-module-system-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>co.yixiang.boot</groupId>
<artifactId>yshop-module-infra-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>co.yixiang.boot</groupId>
<artifactId>yshop-module-coupon-biz</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>co.yixiang.boot</groupId>
<artifactId>yshop-module-shop-biz</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-mp-spring-boot-starter</artifactId> <!-- 微信登录(公众号) -->
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-miniapp-spring-boot-starter</artifactId> <!-- 微信登录(小程序) -->
</dependency>
<dependency>
<groupId>co.yixiang.boot</groupId>
<artifactId>yshop-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>co.yixiang.boot</groupId>
<artifactId>yshop-spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>co.yixiang.boot</groupId>
<artifactId>yshop-spring-boot-starter-mybatis</artifactId>
</dependency>
<dependency>
<groupId>co.yixiang.boot</groupId>
<artifactId>yshop-spring-boot-starter-redis</artifactId>
</dependency>
<!-- 消息队列相关 -->
<dependency>
<groupId>co.yixiang.boot</groupId>
<artifactId>yshop-spring-boot-starter-mq</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>co.yixiang.boot</groupId>
<artifactId>yshop-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>co.yixiang.boot</groupId>
<artifactId>yshop-spring-boot-starter-biz-ip</artifactId>
</dependency>
<!-- 工具类相关 -->
</dependencies>
</project>
@@ -0,0 +1,79 @@
package co.yixiang.yshop.module.member.api.user;
import cn.hutool.core.util.IdUtil;
import co.yixiang.yshop.framework.common.enums.CommonStatusEnum;
import co.yixiang.yshop.framework.common.enums.ShopCommonEnum;
import co.yixiang.yshop.module.member.api.user.dto.MemberUserRespDTO;
import co.yixiang.yshop.module.member.api.user.dto.WechatUserDto;
import co.yixiang.yshop.module.member.convert.user.UserConvert;
import co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;
import co.yixiang.yshop.module.member.service.user.MemberUserService;
import jakarta.annotation.Resource;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.List;
/**
* 会员用户的 API 实现类
*
* @author yshop
*/
@Service
@Validated
public class MemberUserApiImpl implements MemberUserApi {
@Resource
private MemberUserService userService;
@Resource
private PasswordEncoder passwordEncoder;
@Override
public void saveWechatMember(WechatUserDto wechatUserDto) {
MemberUserDO user = new MemberUserDO();
user.setNickname(wechatUserDto.getNickname());
user.setAvatar(wechatUserDto.getHeadimgurl());
// 生成密码
String password = IdUtil.fastSimpleUUID();
user.setPassword(encodePassword(password));
user.setUsername(wechatUserDto.getOpenid());
user.setLoginType("wechat");
user.setWxProfile(wechatUserDto);
userService.save(user);
}
@Override
public MemberUserRespDTO getUser(Long id) {
MemberUserDO user = userService.getUser(id);
return UserConvert.INSTANCE.convert2(user);
}
@Override
public List<MemberUserRespDTO> getUsers(Collection<Long> ids) {
return UserConvert.INSTANCE.convertList2(userService.getUserList(ids));
}
@Override
public List<MemberUserRespDTO> getUserListByNickname(String nickname) {
return UserConvert.INSTANCE.convertList2(userService.getUserListByNickname(nickname));
}
@Override
public MemberUserRespDTO getUserByMobile(String mobile) {
return UserConvert.INSTANCE.convert2(userService.getUserByMobile(mobile));
}
/**
* 对密码进行加密
*
* @param password 密码
* @return 加密后的密码
*/
private String encodePassword(String password) {
return passwordEncoder.encode(password);
}
}
@@ -0,0 +1,92 @@
package co.yixiang.yshop.module.member.controller.admin.user;
import co.yixiang.yshop.framework.common.pojo.CommonResult;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import co.yixiang.yshop.module.member.controller.admin.user.vo.*;
import co.yixiang.yshop.module.member.convert.user.UserConvert;
import co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;
import co.yixiang.yshop.module.member.service.user.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Collection;
import java.util.List;
import static co.yixiang.yshop.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 用户")
@RestController
@RequestMapping("/member/user")
@Validated
public class MemberUserController {
@Resource
private UserService userService;
@PostMapping("/create")
@Operation(summary = "创建用户")
@PreAuthorize("@ss.hasPermission('member:user:create')")
public CommonResult<Long> createUser(@Valid @RequestBody UserCreateReqVO createReqVO) {
return success(userService.createUser(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新用户")
@PreAuthorize("@ss.hasPermission('member:user:update')")
public CommonResult<Boolean> updateUser(@Valid @RequestBody UserUpdateReqVO updateReqVO) {
userService.updateUser(updateReqVO);
return success(true);
}
@PutMapping("/updateMony")
@Operation(summary = "更新用户余额与积分")
@PreAuthorize("@ss.hasPermission('member:user:update')")
public CommonResult<Boolean> updateMony(@Valid @RequestBody UserUpdateMoneyReqVO updateReqVO) {
userService.updateMony(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除用户")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('member:user:delete')")
public CommonResult<Boolean> deleteUser(@RequestParam("id") Long id) {
userService.deleteUser(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得用户")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('member:user:query')")
public CommonResult<UserRespVO> getUser(@RequestParam("id") Long id) {
MemberUserDO user = userService.getUser(id);
return success(UserConvert.INSTANCE.convert(user,true));
}
@GetMapping("/list")
@Operation(summary = "获得用户列表")
@Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
@PreAuthorize("@ss.hasPermission('member:user:query')")
public CommonResult<List<UserRespVO>> getUserList(@RequestParam("ids") Collection<Long> ids) {
List<MemberUserDO> list = userService.getUserList(ids);
return success(UserConvert.INSTANCE.convertList(list));
}
@GetMapping("/page")
@Operation(summary = "获得用户分页")
@PreAuthorize("@ss.hasPermission('member:user:query')")
public CommonResult<PageResult<UserRespVO>> getUserPage(@Valid UserPageReqVO pageVO) {
PageResult<MemberUserDO> pageResult = userService.getUserPage(pageVO);
return success(UserConvert.INSTANCE.convertPage(pageResult));
}
}
@@ -0,0 +1,106 @@
package co.yixiang.yshop.module.member.controller.admin.user.vo;
import co.yixiang.yshop.module.member.api.user.dto.WechatUserDto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
/**
* 用户 Base VO,提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class UserBaseVO {
@Schema(description = "用户账户(跟accout一样)", example = "王五")
private String username;
@Schema(description = "真实姓名", example = "王五")
private String realName;
@Schema(description = "用户昵称", example = "李四")
private String nickname;
@Schema(description = "用户头像")
private String avatar;
//@MobileDesensitize
@Schema(description = "手机号码")
private String mobile;
@Schema(description = "添加ip")
private String addIp;
@Schema(description = "用户余额", required = true)
//@NotNull(message = "用户余额不能为空")
private BigDecimal nowMoney;
@Schema(description = "佣金金额", required = true, example = "14395")
//@NotNull(message = "佣金金额不能为空")
private BigDecimal brokeragePrice;
@Schema(description = "用户剩余积分", required = true)
// @NotNull(message = "用户剩余积分不能为空")
private BigDecimal integral;
@Schema(description = "1为正常,0为禁止", required = true, example = "2")
// @NotNull(message = "1为正常,0为禁止不能为空")
private Integer status;
@Schema(description = "是否为推广员", required = true)
// @NotNull(message = "是否为推广员不能为空")
private Integer isPromoter;
@Schema(description = "用户购买次数", example = "16061")
private Integer payCount;
@Schema(description = "下级人数", example = "4960")
private Integer spreadCount;
@Schema(description = "详细地址", required = true)
// @NotNull(message = "详细地址不能为空")
private String addres;
@Schema(description = "管理员编号 ", example = "29490")
private Integer adminid;
@Schema(description = "用户登陆类型,h5,wechat,routine", required = true, example = "2")
// @NotNull(message = "用户登陆类型,h5,wechat,routine不能为空")
private String loginType;
@Schema(description = "微信用户json信息")
private WechatUserDto wxProfile;
@Schema(description = "生日")
private String birthday;
/**
* 最后一次登录ip
*/
private String loginIp;
/**
* 最后一次登录ip
*/
private String lastIp;
/**
* 等级
*/
private Integer level;
/**
* 推广元id
*/
private Long spreadUid;
/**
* 身份证号码
*/
private String cardId;
}
@@ -0,0 +1,57 @@
package co.yixiang.yshop.module.member.controller.admin.user.vo;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 用户创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class UserCreateReqVO extends UserBaseVO {
@Schema(description = "用户密码(跟pwd)")
private String password;
@Schema(description = "生日")
private String birthday;
@Schema(description = "身份证号码", example = "29961")
private String cardId;
@Schema(description = "用户备注")
private String mark;
@Schema(description = "合伙人id", example = "4234")
private Integer partnerId;
@Schema(description = "用户分组id", example = "12625")
private Integer groupId;
@Schema(description = "最后一次登录ip")
private String lastIp;
@Schema(description = "连续签到天数", required = true)
@NotNull(message = "连续签到天数不能为空")
private Integer signNum;
@Schema(description = "等级", required = true)
@NotNull(message = "等级不能为空")
private Integer level;
@Schema(description = "推广元id", example = "5747")
private Long spreadUid;
@Schema(description = "推广员关联时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime spreadTime;
@Schema(description = "用户类型", required = true, example = "1")
@NotNull(message = "用户类型不能为空")
private String userType;
}
@@ -0,0 +1,32 @@
package co.yixiang.yshop.module.member.controller.admin.user.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import co.yixiang.yshop.framework.common.pojo.PageParam;
import java.time.LocalDateTime;
import org.springframework.format.annotation.DateTimeFormat;
import static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 用户 Excel 导出 Request VO,参数和 UserPageReqVO 是一致的")
@Data
public class UserExportReqVO {
@Schema(description = "用户账户(跟accout一样)", example = "王五")
private String username;
@Schema(description = "真实姓名", example = "王五")
private String realName;
@Schema(description = "用户昵称", example = "李四")
private String nickname;
@Schema(description = "手机号码")
private String phone;
@Schema(description = "添加时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}
@@ -0,0 +1,37 @@
package co.yixiang.yshop.module.member.controller.admin.user.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import co.yixiang.yshop.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 用户分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class UserPageReqVO extends PageParam {
@Schema(description = "用户账户(跟accout一样)", example = "王五")
private String username;
@Schema(description = "真实姓名", example = "王五")
private String realName;
@Schema(description = "用户昵称", example = "李四")
private String nickname;
@Schema(description = "手机号码")
private String mobile;
@Schema(description = "添加时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
@Schema(description = "登录类型")
private String loginType;
}
@@ -0,0 +1,19 @@
package co.yixiang.yshop.module.member.controller.admin.user.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 用户 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class UserRespVO extends UserBaseVO {
@Schema(description = "用户id", required = true, example = "16370")
private Long id;
@Schema(description = "添加时间")
private LocalDateTime createTime;
}
@@ -0,0 +1,33 @@
package co.yixiang.yshop.module.member.controller.admin.user.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.ToString;
@Schema(description = "管理后台 - 用户更新 Request VO")
@Data
@ToString(callSuper = true)
public class UserUpdateMoneyReqVO {
@Schema(description = "用户id", required = true, example = "16370")
@NotNull(message = "用户id不能为空")
private Long id;
@Schema(description = "修改金额类型")
private Integer ptype;
@Schema(description = "金额")
@NotNull(message = "金额不能为空")
private String money;
@Schema(description = "修改积分类型")
private Integer itype;
@Schema(description = "积分")
@NotNull(message = "积分不能为空")
private String integral;
}
@@ -0,0 +1,60 @@
package co.yixiang.yshop.module.member.controller.admin.user.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 用户更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class UserUpdateReqVO extends UserBaseVO {
@Schema(description = "用户id", required = true, example = "16370")
@NotNull(message = "用户id不能为空")
private Long id;
@Schema(description = "用户密码(跟pwd)")
private String password;
@Schema(description = "生日")
private String birthday;
@Schema(description = "身份证号码", example = "29961")
private String cardId;
@Schema(description = "用户备注")
private String mark;
@Schema(description = "合伙人id", example = "4234")
private Integer partnerId;
@Schema(description = "用户分组id", example = "12625")
private Integer groupId;
@Schema(description = "最后一次登录ip")
private String lastIp;
@Schema(description = "连续签到天数", required = true)
//@NotNull(message = "连续签到天数不能为空")
private Integer signNum;
@Schema(description = "等级", required = true)
//@NotNull(message = "等级不能为空")
private Integer level;
@Schema(description = "推广元id", example = "5747")
private Long spreadUid;
@Schema(description = "推广员关联时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime spreadTime;
@Schema(description = "用户类型", required = true, example = "1")
// @NotNull(message = "用户类型不能为空")
private String userType;
}
@@ -0,0 +1,87 @@
package co.yixiang.yshop.module.member.controller.admin.useraddress;
import co.yixiang.yshop.framework.common.pojo.CommonResult;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import co.yixiang.yshop.module.member.controller.admin.useraddress.vo.UserAddressCreateReqVO;
import co.yixiang.yshop.module.member.controller.admin.useraddress.vo.UserAddressPageReqVO;
import co.yixiang.yshop.module.member.controller.admin.useraddress.vo.UserAddressRespVO;
import co.yixiang.yshop.module.member.controller.admin.useraddress.vo.UserAddressUpdateReqVO;
import co.yixiang.yshop.module.member.convert.useraddress.UserAddressConvert;
import co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;
import co.yixiang.yshop.module.member.service.useraddress.UserAddressService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Collection;
import java.util.List;
import static co.yixiang.yshop.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 用户地址")
@RestController
@RequestMapping("/member/user-address")
@Validated
public class UserAddressController {
@Resource
private UserAddressService userAddressService;
@PostMapping("/create")
@Operation(summary = "创建用户地址")
@PreAuthorize("@ss.hasPermission('member:user-address:create')")
public CommonResult<Long> createUserAddress(@Valid @RequestBody UserAddressCreateReqVO createReqVO) {
return success(userAddressService.createUserAddress(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新用户地址")
@PreAuthorize("@ss.hasPermission('member:user-address:update')")
public CommonResult<Boolean> updateUserAddress(@Valid @RequestBody UserAddressUpdateReqVO updateReqVO) {
userAddressService.updateUserAddress(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除用户地址")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('member:user-address:delete')")
public CommonResult<Boolean> deleteUserAddress(@RequestParam("id") Long id) {
userAddressService.deleteUserAddress(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得用户地址")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('member:user-address:query')")
public CommonResult<UserAddressRespVO> getUserAddress(@RequestParam("id") Long id) {
UserAddressDO userAddress = userAddressService.getUserAddress(id);
return success(UserAddressConvert.INSTANCE.convert(userAddress));
}
@GetMapping("/list")
@Operation(summary = "获得用户地址列表")
@Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
@PreAuthorize("@ss.hasPermission('member:user-address:query')")
public CommonResult<List<UserAddressRespVO>> getUserAddressList(@RequestParam("ids") Collection<Long> ids) {
List<UserAddressDO> list = userAddressService.getUserAddressList(ids);
return success(UserAddressConvert.INSTANCE.convertList(list));
}
@GetMapping("/page")
@Operation(summary = "获得用户地址分页")
@PreAuthorize("@ss.hasPermission('member:user-address:query')")
public CommonResult<PageResult<UserAddressRespVO>> getUserAddressPage(@Valid UserAddressPageReqVO pageVO) {
PageResult<UserAddressDO> pageResult = userAddressService.getUserAddressPage(pageVO);
return success(UserAddressConvert.INSTANCE.convertPage(pageResult));
}
}
@@ -0,0 +1,69 @@
package co.yixiang.yshop.module.member.controller.admin.useraddress.vo;
import co.yixiang.yshop.framework.desensitize.core.slider.annotation.MobileDesensitize;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
/**
* 用户地址 Base VO,提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class UserAddressBaseVO {
@Schema(description = "用户id", required = true, example = "25124")
@NotNull(message = "用户id不能为空")
private Long uid;
@Schema(description = "收货人姓名", required = true, example = "李四")
@NotNull(message = "收货人姓名不能为空")
private String realName;
@MobileDesensitize
@Schema(description = "收货人电话", required = true)
@NotNull(message = "收货人电话不能为空")
private String phone;
@Schema(description = "收货人所在省", required = true)
@NotNull(message = "收货人所在省不能为空")
private String province;
@Schema(description = "收货人所在市", required = true)
@NotNull(message = "收货人所在市不能为空")
private String city;
@Schema(description = "城市id", example = "15595")
private Integer cityId;
@Schema(description = "收货人所在区", required = true)
@NotNull(message = "收货人所在区不能为空")
private String district;
@Schema(description = "收货人详细地址", required = true)
@NotNull(message = "收货人详细地址不能为空")
private String detail;
@Schema(description = "邮编", required = true)
@NotNull(message = "邮编不能为空")
private String postCode;
@Schema(description = "经度", required = true)
@NotNull(message = "经度不能为空")
private String longitude;
@Schema(description = "纬度", required = true)
@NotNull(message = "纬度不能为空")
private String latitude;
@Schema(description = "是否默认", required = true)
@NotNull(message = "是否默认不能为空")
private Integer isDefault;
private String address;
}
@@ -0,0 +1,13 @@
package co.yixiang.yshop.module.member.controller.admin.useraddress.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "管理后台 - 用户地址创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class UserAddressCreateReqVO extends UserAddressBaseVO {
}
@@ -0,0 +1,56 @@
package co.yixiang.yshop.module.member.controller.admin.useraddress.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import co.yixiang.yshop.framework.common.pojo.PageParam;
import java.time.LocalDateTime;
import org.springframework.format.annotation.DateTimeFormat;
import static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 用户地址 Excel 导出 Request VO,参数和 UserAddressPageReqVO 是一致的")
@Data
public class UserAddressExportReqVO {
@Schema(description = "用户id", example = "25124")
private Long uid;
@Schema(description = "收货人姓名", example = "李四")
private String realName;
@Schema(description = "收货人电话")
private String phone;
@Schema(description = "收货人所在省")
private String province;
@Schema(description = "收货人所在市")
private String city;
@Schema(description = "城市id", example = "15595")
private Integer cityId;
@Schema(description = "收货人所在区")
private String district;
@Schema(description = "收货人详细地址")
private String detail;
@Schema(description = "邮编")
private String postCode;
@Schema(description = "经度")
private String longitude;
@Schema(description = "纬度")
private String latitude;
@Schema(description = "是否默认")
private Byte isDefault;
@Schema(description = "添加时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}
@@ -0,0 +1,58 @@
package co.yixiang.yshop.module.member.controller.admin.useraddress.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import co.yixiang.yshop.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 用户地址分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class UserAddressPageReqVO extends PageParam {
@Schema(description = "用户id", example = "25124")
private Long uid;
@Schema(description = "收货人姓名", example = "李四")
private String realName;
@Schema(description = "收货人电话")
private String phone;
@Schema(description = "收货人所在省")
private String province;
@Schema(description = "收货人所在市")
private String city;
@Schema(description = "城市id", example = "15595")
private Integer cityId;
@Schema(description = "收货人所在区")
private String district;
@Schema(description = "收货人详细地址")
private String detail;
@Schema(description = "邮编")
private String postCode;
@Schema(description = "经度")
private String longitude;
@Schema(description = "纬度")
private String latitude;
@Schema(description = "是否默认")
private Byte isDefault;
@Schema(description = "添加时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}
@@ -0,0 +1,19 @@
package co.yixiang.yshop.module.member.controller.admin.useraddress.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 用户地址 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class UserAddressRespVO extends UserAddressBaseVO {
@Schema(description = "用户地址id", required = true, example = "24169")
private Long id;
@Schema(description = "添加时间", required = true)
private LocalDateTime createTime;
}
@@ -0,0 +1,19 @@
package co.yixiang.yshop.module.member.controller.admin.useraddress.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import java.util.*;
@Schema(description = "管理后台 - 用户地址更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class UserAddressUpdateReqVO extends UserAddressBaseVO {
@Schema(description = "用户地址id", required = true, example = "24169")
@NotNull(message = "用户地址id不能为空")
private Long id;
}
@@ -0,0 +1,47 @@
package co.yixiang.yshop.module.member.controller.admin.userbill;
import co.yixiang.yshop.framework.common.pojo.CommonResult;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import co.yixiang.yshop.module.member.controller.admin.userbill.vo.UserBillPageReqVO;
import co.yixiang.yshop.module.member.controller.admin.userbill.vo.UserBillRespVO;
import co.yixiang.yshop.module.member.convert.userbill.UserBillConvert;
import co.yixiang.yshop.module.member.dal.dataobject.userbill.UserBillDO;
import co.yixiang.yshop.module.member.service.userbill.UserBillService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static co.yixiang.yshop.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 用户账单")
@RestController
@RequestMapping("/member/user-bill")
@Validated
public class UserBillController {
@Resource
private UserBillService userBillService;
@GetMapping("/page")
@Operation(summary = "获得用户账单分页")
@PreAuthorize("@ss.hasPermission('member:user-bill:query')")
public CommonResult<PageResult<UserBillRespVO>> getUserBillPage(@Valid UserBillPageReqVO pageVO) {
PageResult<UserBillDO> pageResult = userBillService.getUserBillPage(pageVO);
return success(UserBillConvert.INSTANCE.convertPage(pageResult));
}
}
@@ -0,0 +1,60 @@
package co.yixiang.yshop.module.member.controller.admin.userbill.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import java.util.*;
import java.math.BigDecimal;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
/**
* 用户账单 Base VO,提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class UserBillBaseVO {
@Schema(description = "用户uid", required = true, example = "9419")
@NotNull(message = "用户uid不能为空")
private Long uid;
@Schema(description = "关联id", required = true, example = "18439")
@NotNull(message = "关联id不能为空")
private String linkId;
@Schema(description = "0 = 支出 1 = 获得", required = true)
@NotNull(message = "0 = 支出 1 = 获得不能为空")
private Byte pm;
@Schema(description = "账单标题", required = true)
@NotNull(message = "账单标题不能为空")
private String title;
@Schema(description = "明细种类", required = true)
@NotNull(message = "明细种类不能为空")
private String category;
@Schema(description = "明细类型", required = true, example = "2")
@NotNull(message = "明细类型不能为空")
private String type;
@Schema(description = "明细数字", required = true)
@NotNull(message = "明细数字不能为空")
private BigDecimal number;
@Schema(description = "剩余", required = true)
@NotNull(message = "剩余不能为空")
private BigDecimal balance;
@Schema(description = "备注", required = true)
@NotNull(message = "备注不能为空")
private String mark;
@Schema(description = "0 = 带确定 1 = 有效 -1 = 无效", required = true, example = "1")
@NotNull(message = "0 = 带确定 1 = 有效 -1 = 无效不能为空")
private Integer status;
}
@@ -0,0 +1,13 @@
package co.yixiang.yshop.module.member.controller.admin.userbill.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "管理后台 - 用户账单创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class UserBillCreateReqVO extends UserBillBaseVO {
}
@@ -0,0 +1,52 @@
package co.yixiang.yshop.module.member.controller.admin.userbill.vo;
import lombok.*;
import java.math.BigDecimal;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import co.yixiang.yshop.framework.common.pojo.PageParam;
import java.time.LocalDateTime;
import org.springframework.format.annotation.DateTimeFormat;
import static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 用户账单 Excel 导出 Request VO,参数和 UserBillPageReqVO 是一致的")
@Data
public class UserBillExportReqVO {
@Schema(description = "用户uid", example = "9419")
private Long uid;
@Schema(description = "关联id", example = "18439")
private String linkId;
@Schema(description = "0 = 支出 1 = 获得")
private Byte pm;
@Schema(description = "账单标题")
private String title;
@Schema(description = "明细种类")
private String category;
@Schema(description = "明细类型", example = "2")
private String type;
@Schema(description = "明细数字")
private BigDecimal number;
@Schema(description = "剩余")
private BigDecimal balance;
@Schema(description = "备注")
private String mark;
@Schema(description = "添加时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
@Schema(description = "0 = 带确定 1 = 有效 -1 = 无效", example = "1")
private Boolean status;
}
@@ -0,0 +1,54 @@
package co.yixiang.yshop.module.member.controller.admin.userbill.vo;
import lombok.*;
import java.math.BigDecimal;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import co.yixiang.yshop.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 用户账单分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class UserBillPageReqVO extends PageParam {
@Schema(description = "用户uid", example = "9419")
private Long uid;
@Schema(description = "关联id", example = "18439")
private String linkId;
@Schema(description = "0 = 支出 1 = 获得")
private Byte pm;
@Schema(description = "账单标题")
private String title;
@Schema(description = "明细种类")
private String category;
@Schema(description = "明细类型", example = "2")
private String type;
@Schema(description = "明细数字")
private BigDecimal number;
@Schema(description = "剩余")
private BigDecimal balance;
@Schema(description = "备注")
private String mark;
@Schema(description = "添加时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
@Schema(description = "0 = 带确定 1 = 有效 -1 = 无效", example = "1")
private Boolean status;
}
@@ -0,0 +1,19 @@
package co.yixiang.yshop.module.member.controller.admin.userbill.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 用户账单 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class UserBillRespVO extends UserBillBaseVO {
@Schema(description = "用户账单id", required = true, example = "22559")
private Long id;
@Schema(description = "添加时间", required = true)
private LocalDateTime createTime;
}
@@ -0,0 +1,18 @@
package co.yixiang.yshop.module.member.controller.admin.userbill.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.*;
@Schema(description = "管理后台 - 用户账单更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class UserBillUpdateReqVO extends UserBillBaseVO {
@Schema(description = "用户账单id", required = true, example = "22559")
@NotNull(message = "用户账单id不能为空")
private Long id;
}
@@ -0,0 +1,136 @@
/**
* Copyright (C) 2018-2022
* All rights reserved, Designed By www.yixiang.co
* 注意:
* 本软件为www.yixiang.co开发研制,未经购买不得使用
* 购买后可获得全部源代码(禁止转卖、分享、上传到码云、github等开源平台)
* 一经发现盗用、分享等行为,将追究法律责任,后果自负
*/
package co.yixiang.yshop.module.member.controller.app.address;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import co.yixiang.yshop.framework.common.pojo.CommonResult;
import co.yixiang.yshop.framework.common.util.yshop.LocationUtils;
import co.yixiang.yshop.framework.ip.core.Area;
import co.yixiang.yshop.framework.ip.core.utils.AreaUtils;
import co.yixiang.yshop.framework.security.core.annotations.PreAuthenticated;
import co.yixiang.yshop.module.member.controller.app.address.param.AppAddressParam;
import co.yixiang.yshop.module.member.controller.app.address.vo.AppUserAddressLocationVo;
import co.yixiang.yshop.module.member.controller.app.address.vo.AppUserAddressQueryVo;
import co.yixiang.yshop.module.member.controller.app.address.vo.AreaNodeRespVO;
import co.yixiang.yshop.module.member.convert.useraddress.AreaConvert;
import co.yixiang.yshop.module.member.service.useraddress.AppUserAddressService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;
import static co.yixiang.yshop.framework.common.pojo.CommonResult.success;
import static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.USER_ADDRESS_PARAM_NOT_EXISTS;
/**
* <p>
* 用户地前端控制器
* </p>
*
* @author hupeng
* @since 2023-6-28
*/
@Slf4j
@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Tag(name = "用户 APP - 用户地址")
@RequestMapping("/address")
public class AppUserAddressController {
private final AppUserAddressService appUserAddressService;
@GetMapping("/city_list")
@Operation(summary = "城市列表")
public CommonResult<List<AreaNodeRespVO>> getTest() {
Area area = AreaUtils.getArea(Area.ID_CHINA);
Assert.notNull(area, "获取不到中国");
return success(AreaConvert.INSTANCE.convertList(area.getChildren()));
}
/**
* 添加或修改地址
*/
@PreAuthenticated
@PostMapping("/addAndEdit")
@Operation(summary = "添加或修改地址")
public CommonResult<Long> addYxUserAddress(@RequestBody @Valid AppAddressParam param){
Long uid = getLoginUserId();
Long id = appUserAddressService.addAndEdit(uid,param);
return success(id);
}
/**
* 设置默认地址
*/
@PreAuthenticated
@PostMapping("/default/set/{id}")
@Parameter(name = "id", description = "地址id", required = true)
@Operation(summary = "设置默认地址")
public CommonResult<Boolean> setDefault(@PathVariable String id){
Long uid = getLoginUserId();
appUserAddressService.setDefault(uid,id);
return success(true);
}
/**
* 删除用户地址
*/
@PreAuthenticated
@PostMapping("/del/{id}")
@Operation(summary = "删除用户地址")
public CommonResult<Boolean> deleteYxUserAddress(@PathVariable String id){
if(StrUtil.isBlank(id) || !NumberUtil.isNumber(id)){
throw exception(USER_ADDRESS_PARAM_NOT_EXISTS);
}
appUserAddressService.removeById(id);
return success(true);
}
/**
* 用户地址列表
*/
@PreAuthenticated
@GetMapping("/list")
@Operation(summary = "用户地址列表")
public CommonResult<List<AppUserAddressQueryVo>> getYxUserAddressPageList(@RequestParam(value = "page",defaultValue = "1") int page,
@RequestParam(value = "limit",defaultValue = "10") int limit){
Long uid = getLoginUserId();
return success(appUserAddressService.getList(uid,page,limit));
}
/**
* 用户地址列表
*/
@PostMapping("/getDistanceFromLocation")
@Operation(summary = "用户地址列表")
public CommonResult<Double> getDistanceFromLocation( @RequestBody AppUserAddressLocationVo addressLocation){
return success(LocationUtils.getDistance(addressLocation.getLat(),addressLocation.getLng(),
addressLocation.getLat2(),addressLocation.getLng2()));
}
}
@@ -0,0 +1,27 @@
package co.yixiang.yshop.module.member.controller.app.address.param;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* @ClassName AddressDetailParam
* @Author hupeng <610796224@qq.com>
* @Date 2023/6/28
**/
@Data
public class AddressDetailParam implements Serializable {
@Schema(description = "城市ID", required = true)
private Integer cityId;
@Schema(description = "城市", required = true)
private String city;
@Schema(description = "地区", required = true)
private String district;
@Schema(description = "省份", required = true)
private String province;
}
@@ -0,0 +1,51 @@
package co.yixiang.yshop.module.member.controller.app.address.param;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serializable;
/**
* @ClassName AddressParam
* @Author hupeng <610796224@qq.com>
* @Date 2023/6/28
**/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class AppAddressParam implements Serializable {
@Schema(description = "地址ID", required = true)
private String id;
@NotBlank
@Size(min = 1, max = 30,message = "长度超过了限制")
@Schema(description = "收货地址真实名字", required = true)
private String realName;
@Schema(description = "收货地址邮编", required = true)
private String postCode;
@Schema(description = "是否默认收货地址 1是 0否", required = true)
private Integer isDefault;
@NotBlank
@Size(min = 1, max = 60,message = "长度超过了限制")
@Schema(description = "收货详细地址", required = true)
private String detail;
@NotBlank
@Schema(description = "收货手机号码", required = true)
private String phone;
@Schema(description = "收货地址详情", required = true)
private String address;
@Schema(description = "经度", required = true)
private String longitude;
@Schema(description = "纬度", required = true)
private String latitude;
}
@@ -0,0 +1,35 @@
package co.yixiang.yshop.module.member.controller.app.address.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* <p>
* 用户地址表 查询结果对象
* </p>
*
* @author hupeng
* @date 2023-6-28
*/
@Data
@Schema(description = "用户地址表查询参数")
public class AppUserAddressLocationVo implements Serializable {
private static final long serialVersionUID = 1L;
private double lat;
private double lng;
private double lat2;
private double lng2;
}
@@ -0,0 +1,49 @@
package co.yixiang.yshop.module.member.controller.app.address.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* <p>
* 用户地址表 查询结果对象
* </p>
*
* @author hupeng
* @date 2023-6-28
*/
@Data
@Schema(description = "用户地址表查询参数")
public class AppUserAddressQueryVo implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "用户地址id", required = true, example = "24169")
private Long id;
@Schema(description = "用户id", required = true, example = "24169")
private Long uid;
@Schema(description = "收货人姓名", required = true, example = "24169")
private String realName;
@Schema(description = "收货人电话", required = true, example = "24169")
private String phone;
@Schema(description = "收货人所在省", required = true, example = "24169")
private String address;
@Schema(description = "收货人详细地址", required = true, example = "24169")
private String detail;
@Schema(description = "经度", required = true, example = "24169")
private String longitude;
@Schema(description = "纬度", required = true, example = "24169")
private String latitude;
@Schema(description = "是否默认", required = true, example = "24169")
private Integer isDefault;
}
@@ -0,0 +1,23 @@
package co.yixiang.yshop.module.member.controller.app.address.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - 地区节点 Response VO")
@Data
public class AreaNodeRespVO {
@Schema(description = "编号", required = true, example = "110000")
private Integer id;
@Schema(description = "名字", required = true, example = "北京")
private String name;
/**
* 子节点
*/
private List<AreaNodeRespVO> children;
}
@@ -0,0 +1,140 @@
package co.yixiang.yshop.module.member.controller.app.auth;
import cn.hutool.core.util.StrUtil;
import co.yixiang.yshop.framework.common.pojo.CommonResult;
import co.yixiang.yshop.framework.security.config.SecurityProperties;
import co.yixiang.yshop.framework.security.core.annotations.PreAuthenticated;
import co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;
import co.yixiang.yshop.module.member.controller.app.auth.vo.*;
import co.yixiang.yshop.module.member.service.auth.MemberAuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static co.yixiang.yshop.framework.common.pojo.CommonResult.success;
import static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "用户 APP - 认证")
@RestController
@RequestMapping("/member/auth")
@Validated
@Slf4j
public class AppAuthController {
@Resource
private MemberAuthService authService;
@Resource
private SecurityProperties securityProperties;
@Value("${yshop.info.isActive}")
private Boolean isActive;
@PostMapping("/login")
@Operation(summary = "使用手机 + 密码登录")
public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {
return success(authService.login(reqVO));
}
@PostMapping("/logout")
@PermitAll
@Operation(summary = "登出系统")
public CommonResult<Boolean> logout(HttpServletRequest request) {
String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader(),securityProperties.getTokenParameter());
if (StrUtil.isNotBlank(token)) {
authService.logout(token);
}
return success(true);
}
@PostMapping("/refresh-token")
@Operation(summary = "刷新令牌")
@Parameter(name = "refreshToken", description = "刷新令牌", required = true)
public CommonResult<AppAuthLoginRespVO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
return success(authService.refreshToken(refreshToken));
}
// ========== 短信登录相关 ==========
@PostMapping("/sms-login")
@Operation(summary = "使用手机 + 验证码登录")
public CommonResult<AppAuthLoginRespVO> smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) {
return success(authService.smsLogin(reqVO));
}
@PostMapping("/send-sms-code")
@Operation(summary = "发送手机验证码")
public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid AppAuthSmsSendReqVO reqVO) {
authService.sendSmsCode(getLoginUserId(), reqVO);
return success(true);
}
// @PostMapping("/reset-password")
// @Operation(summary = "重置密码", description = "用户忘记密码时使用")
// @PreAuthenticated
// public CommonResult<Boolean> resetPassword(@RequestBody @Valid AppAuthResetPasswordReqVO reqVO) {
// authService.resetPassword(reqVO);
// return success(true);
// }
@PostMapping("/update-password")
@Operation(summary = "修改用户密码", description = "用户修改密码时使用")
@PreAuthenticated
public CommonResult<Boolean> updatePassword(@RequestBody @Valid AppAuthUpdatePasswordReqVO reqVO) {
authService.updatePassword(getLoginUserId(), reqVO);
return success(true);
}
// ========== 社交登录相关 ==========
// @GetMapping("/social-auth-redirect")
// @Operation(summary = "社交授权的跳转")
// @Parameters({
// @Parameter(name = "type", description = "社交类型", required = true),
// @Parameter(name = "redirectUri", description = "回调路径")
// })
// public CommonResult<String> socialAuthRedirect(@RequestParam("type") Integer type,
// @RequestParam("redirectUri") String redirectUri) {
// return CommonResult.success(authService.getSocialAuthorizeUrl(type, redirectUri));
// }
//
// @PostMapping("/social-login")
// @Operation(summary = "社交快捷登录,使用 code 授权码", description = "适合未登录的用户,但是社交账号已绑定用户")
// public CommonResult<AppAuthLoginRespVO> socialLogin(@RequestBody @Valid AppAuthSocialLoginReqVO reqVO) {
// return success(authService.socialLogin(reqVO));
// }
@PostMapping("/weixin-mini-app-login")
@Operation(summary = "微信小程序的一键登录")
public CommonResult<AppAuthLoginRespVO> weixinMiniAppLogin(@RequestBody @Valid AppAuthWeixinMiniAppLoginReqVO reqVO) {
return success(authService.weixinMiniAppLogin(reqVO));
}
@PostMapping("/auth-session")
@Operation(summary = "微信小程序登录")
public CommonResult<AppAuthLoginRespVO> weixinMiniAppLogin2(@RequestBody @Valid AppWeixinMiniLoginVO loginVO) {
AppAuthLoginRespVO appAuthLoginRespVO = authService.weixinMiniAppLogin2(loginVO);
appAuthLoginRespVO.setIsActive(isActive);
return success(appAuthLoginRespVO);
}
@PostMapping("/auth-miniapp-login")
@Operation(summary = "微信小程序登录")
public CommonResult<AppAuthLoginRespVO> weixinMiniAppLogin3(@RequestBody @Valid AppWxMiniLoginVO appWxMiniLoginVO) {
return success(authService.weixinMiniAppLogin3(appWxMiniLoginVO.getEncryptedData(),appWxMiniLoginVO.getIv()
,appWxMiniLoginVO.getOpenid()));
}
}
@@ -0,0 +1,41 @@
package co.yixiang.yshop.module.member.controller.app.auth.vo;
import co.yixiang.yshop.framework.common.validation.InEnum;
import co.yixiang.yshop.framework.common.validation.Mobile;
import co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
// TODO yshop:code review 相关逻辑
@Schema(description = "用户 APP - 校验验证码 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppAuthCheckCodeReqVO {
@Schema(description = "手机号", example = "15601691234")
@NotBlank(message = "手机号不能为空")
@Mobile
private String mobile;
@Schema(description = "手机验证码", required = true, example = "1024")
@NotBlank(message = "手机验证码不能为空")
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
private String code;
@Schema(description = "发送场景,对应 SmsSceneEnum 枚举", example = "1")
@NotNull(message = "发送场景不能为空")
@InEnum(SmsSceneEnum.class)
private Integer scene;
}
@@ -0,0 +1,56 @@
package co.yixiang.yshop.module.member.controller.app.auth.vo;
import cn.hutool.core.util.StrUtil;
import co.yixiang.yshop.framework.common.validation.InEnum;
import co.yixiang.yshop.framework.common.validation.Mobile;
import co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
@Schema(description = "用户 APP - 手机 + 密码登录 Request VO,如果登录并绑定社交用户,需要传递 social 开头的参数")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppAuthLoginReqVO {
@Schema(description = "手机号", required = true, example = "15601691300")
@NotEmpty(message = "手机号不能为空")
@Mobile
private String mobile;
@Schema(description = "密码", required = true, example = "buzhidao")
@NotEmpty(message = "密码不能为空")
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
private String password;
// ========== 绑定社交登录时,需要传递如下参数 ==========
@Schema(description = "社交平台的类型,参见 SysUserSocialTypeEnum 枚举值", required = true, example = "10")
@InEnum(SocialTypeEnum.class)
private Integer socialType;
@Schema(description = "授权码", required = true, example = "1024")
private String socialCode;
@Schema(description = "state", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
private String socialState;
@AssertTrue(message = "授权码不能为空")
public boolean isSocialCodeValid() {
return socialType == null || StrUtil.isNotEmpty(socialCode);
}
@AssertTrue(message = "授权 state 不能为空")
public boolean isSocialState() {
return socialType == null || StrUtil.isNotEmpty(socialState);
}
}
@@ -0,0 +1,37 @@
package co.yixiang.yshop.module.member.controller.app.auth.vo;
import co.yixiang.yshop.module.member.controller.app.user.vo.AppUserQueryVo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Schema(description = "用户 APP - 登录 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppAuthLoginRespVO {
@Schema(description = "用户编号", required = true, example = "1024")
private Long userId;
@Schema(description = "访问令牌", required = true, example = "happy")
private String accessToken;
@Schema(description = "刷新令牌", required = true, example = "nice")
private String refreshToken;
@Schema(description = "过期时间", required = true)
private LocalDateTime expiresTime;
private String openId;
private AppUserQueryVo userInfo;
private Boolean isActive;
}
@@ -0,0 +1,39 @@
package co.yixiang.yshop.module.member.controller.app.auth.vo;
import co.yixiang.yshop.framework.common.validation.Mobile;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
// TODO yshop:code review 相关逻辑
@Schema(description = "用户 APP - 重置密码 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppAuthResetPasswordReqVO {
@Schema(description = "新密码", required = true, example = "buzhidao")
@NotEmpty(message = "新密码不能为空")
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
private String password;
@Schema(description = "手机验证码", required = true, example = "1024")
@NotEmpty(message = "手机验证码不能为空")
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
private String code;
@Schema(description = "手机号",required = true,example = "15878962356")
@NotBlank(message = "手机号不能为空")
@Mobile
private String mobile;
}
@@ -0,0 +1,63 @@
package co.yixiang.yshop.module.member.controller.app.auth.vo;
import cn.hutool.core.util.StrUtil;
import co.yixiang.yshop.framework.common.validation.InEnum;
import co.yixiang.yshop.framework.common.validation.Mobile;
import co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
@Schema(description = "用户 APP - 手机 + 验证码登录 Request VO,如果登录并绑定社交用户,需要传递 social 开头的参数")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppAuthSmsLoginReqVO {
@Schema(description = "手机号", required = true, example = "15601691300")
@NotEmpty(message = "手机号不能为空")
@Mobile
private String mobile;
@Schema(description = "手机验证码", required = true, example = "1024")
@NotEmpty(message = "手机验证码不能为空")
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
private String code;
@Schema(description = "设备来源", required = true, example = "h5")
private String from;
private String openid;
// ========== 绑定社交登录时,需要传递如下参数 ==========
@Schema(description = "社交平台的类型,参见 SysUserSocialTypeEnum 枚举值", required = true, example = "10")
@InEnum(SocialTypeEnum.class)
private Integer socialType;
@Schema(description = "授权码", required = true, example = "1024")
private String socialCode;
@Schema(description = "state", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
private String socialState;
@AssertTrue(message = "授权码不能为空")
public boolean isSocialCodeValid() {
return socialType == null || StrUtil.isNotEmpty(socialCode);
}
@AssertTrue(message = "授权 state 不能为空")
public boolean isSocialState() {
return socialType == null || StrUtil.isNotEmpty(socialState);
}
}
@@ -0,0 +1,26 @@
package co.yixiang.yshop.module.member.controller.app.auth.vo;
import co.yixiang.yshop.framework.common.validation.InEnum;
import co.yixiang.yshop.framework.common.validation.Mobile;
import co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.experimental.Accessors;
@Schema(description = "用户 APP - 发送手机验证码 Request VO")
@Data
@Accessors(chain = true)
public class AppAuthSmsSendReqVO {
@Schema(description = "手机号", example = "15601691234")
@Mobile
private String mobile;
@Schema(description = "发送场景,对应 SmsSceneEnum 枚举", example = "1")
@NotNull(message = "发送场景不能为空")
@InEnum(SmsSceneEnum.class)
private Integer scene;
}
@@ -0,0 +1,34 @@
package co.yixiang.yshop.module.member.controller.app.auth.vo;
import co.yixiang.yshop.framework.common.validation.InEnum;
import co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "用户 APP - 社交快捷登录 Request VO,使用 code 授权码")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppAuthSocialLoginReqVO {
@Schema(description = "社交平台的类型,参见 SysUserSocialTypeEnum 枚举值", required = true, example = "10")
@InEnum(SocialTypeEnum.class)
@NotNull(message = "社交平台的类型不能为空")
private Integer type;
@Schema(description = "授权码", required = true, example = "1024")
@NotEmpty(message = "授权码不能为空")
private String code;
@Schema(description = "state", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
@NotEmpty(message = "state 不能为空")
private String state;
}
@@ -0,0 +1,30 @@
package co.yixiang.yshop.module.member.controller.app.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
// TODO yshop:code review 相关逻辑
@Schema(description = "用户 APP - 修改密码 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppAuthUpdatePasswordReqVO {
@Schema(description = "用户旧密码", required = true, example = "123456")
@NotBlank(message = "旧密码不能为空")
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
private String oldPassword;
@Schema(description = "新密码", required = true, example = "buzhidao")
@NotEmpty(message = "新密码不能为空")
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
private String password;
}
@@ -0,0 +1,26 @@
package co.yixiang.yshop.module.member.controller.app.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "用户 APP - 微信小程序手机登录 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppAuthWeixinMiniAppLoginReqVO {
@Schema(description = "手机 code,小程序通过 wx.getPhoneNumber 方法获得", required = true, example = "hello")
@NotEmpty(message = "手机 code 不能为空")
private String phoneCode;
@Schema(description = "登录 code,小程序通过 wx.login 方法获得", required = true, example = "word")
@NotEmpty(message = "登录 code 不能为空")
private String loginCode;
}
@@ -0,0 +1,23 @@
package co.yixiang.yshop.module.member.controller.app.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "用户 APP - 微信小程序手机登录 VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppWeixinMiniLoginVO {
@Schema(description = "登录 code,小程序通过 wx.login 方法获得", required = true, example = "word")
@NotEmpty(message = "登录 code 不能为空")
private String code;
}
@@ -0,0 +1,31 @@
package co.yixiang.yshop.module.member.controller.app.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "用户 APP - 微信小程序手机登录 VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppWxMiniLoginVO {
@Schema(description = "登录encryptedData", required = true, example = "word")
@NotEmpty(message = "登录encryptedData不能为空")
private String encryptedData;
@Schema(description = "登录iv", required = true, example = "word")
@NotEmpty(message = "登录iv不能为空")
private String iv;
@Schema(description = "登录openid", required = true, example = "word")
@NotEmpty(message = "登录openid不能为空")
private String openid;
}
@@ -0,0 +1,42 @@
package co.yixiang.yshop.module.member.controller.app.social;
import co.yixiang.yshop.framework.common.enums.UserTypeEnum;
import co.yixiang.yshop.framework.common.pojo.CommonResult;
import co.yixiang.yshop.module.member.controller.app.social.vo.AppSocialUserBindReqVO;
import co.yixiang.yshop.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
import co.yixiang.yshop.module.member.convert.social.SocialUserConvert;
import co.yixiang.yshop.module.system.api.social.SocialUserApi;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "用户 App - 社交用户")
@RestController
@RequestMapping("/system/social-user")
@Validated
public class AppSocialUserController {
@Resource
private SocialUserApi socialUserApi;
@PostMapping("/bind")
@Operation(summary = "社交绑定,使用 code 授权码")
public CommonResult<Boolean> socialBind(@RequestBody @Valid AppSocialUserBindReqVO reqVO) {
socialUserApi.bindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.MEMBER.getValue(), reqVO));
return CommonResult.success(true);
}
@DeleteMapping("/unbind")
@Operation(summary = "取消社交绑定")
public CommonResult<Boolean> socialUnbind(@RequestBody AppSocialUserUnbindReqVO reqVO) {
socialUserApi.unbindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.MEMBER.getValue(), reqVO));
return CommonResult.success(true);
}
}
@@ -0,0 +1,34 @@
package co.yixiang.yshop.module.member.controller.app.social.vo;
import co.yixiang.yshop.framework.common.validation.InEnum;
import co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "用户 APP - 社交绑定 Request VO,使用 code 授权码")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppSocialUserBindReqVO {
@Schema(description = "社交平台的类型,参见 SysUserSocialTypeEnum 枚举值", required = true, example = "10")
@InEnum(SocialTypeEnum.class)
@NotNull(message = "社交平台的类型不能为空")
private Integer type;
@Schema(description = "授权码", required = true, example = "1024")
@NotEmpty(message = "授权码不能为空")
private String code;
@Schema(description = "state", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
@NotEmpty(message = "state 不能为空")
private String state;
}
@@ -0,0 +1,30 @@
package co.yixiang.yshop.module.member.controller.app.social.vo;
import co.yixiang.yshop.framework.common.validation.InEnum;
import co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "用户 APP - 取消社交绑定 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppSocialUserUnbindReqVO {
@Schema(description = "社交平台的类型,参见 SysUserSocialTypeEnum 枚举值", required = true, example = "10")
@InEnum(SocialTypeEnum.class)
@NotNull(message = "社交平台的类型不能为空")
private Integer type;
@Schema(description = "社交用户的 openid", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE")
@NotEmpty(message = "社交用户的 openid 不能为空")
private String openid;
}
@@ -0,0 +1,120 @@
package co.yixiang.yshop.module.member.controller.app.user;
import co.yixiang.yshop.framework.common.pojo.CommonResult;
import co.yixiang.yshop.framework.security.core.annotations.PreAuthenticated;
import co.yixiang.yshop.module.member.controller.app.user.vo.*;
import co.yixiang.yshop.module.member.convert.user.UserConvert;
import co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;
import co.yixiang.yshop.module.member.service.user.MemberUserService;
import co.yixiang.yshop.module.member.service.userbill.UserBillService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;
import static co.yixiang.yshop.framework.common.pojo.CommonResult.success;
import static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static co.yixiang.yshop.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY;
@Tag(name = "用户 APP - 用户个人中心")
@RestController
@RequestMapping("/member/user")
@Validated
@Slf4j
public class AppUserController {
@Resource
private MemberUserService userService;
@Resource
private UserBillService userBillService;
@PostMapping("/update-nickname")
@Operation(summary = "修改用户昵称或者生日")
@Parameters({
@Parameter(name = "nickname", description = "用户昵称",
required = true, example = "wang"),
@Parameter(name = "birthday", description = "生日",
required = true, example = "2023-11-12"),
@Parameter(name = "gender", description = "性别",
required = true, example = "o"),
@Parameter(name = "avatar", description = "用户头像",
required = true, example = ""),
@Parameter(name = "mobile", description = "手机",
required = true, example = "")
})
@PreAuthenticated
public CommonResult<Boolean> updateUserNickname(@RequestBody @Valid AppUserNickVO appUserNickVO) {
userService.updateUserNickname(getLoginUserId(), appUserNickVO.getNickname(),appUserNickVO.getBirthday(),
appUserNickVO.getGender(),appUserNickVO.getAvatar(),appUserNickVO.getMobile());
return success(true);
}
@PostMapping("/update-avatar")
@Operation(summary = "修改用户头像")
@PreAuthenticated
public CommonResult<String> updateUserAvatar(@RequestParam("avatarFile") MultipartFile file) throws Exception {
if (file.isEmpty()) {
throw exception(FILE_IS_EMPTY);
}
String avatar = userService.updateUserAvatar(getLoginUserId(), file.getInputStream());
return success(avatar);
}
@GetMapping("/get")
@Operation(summary = "获得基本信息")
@PreAuthenticated
public CommonResult<AppUserInfoRespVO> getUserInfo() {
MemberUserDO user = userService.getUser(getLoginUserId());
return success(UserConvert.INSTANCE.convert(user));
}
@PostMapping("/update-mobile")
@Operation(summary = "修改用户手机")
@PreAuthenticated
public CommonResult<Boolean> updateMobile(@RequestBody @Valid AppUserUpdateMobileReqVO reqVO) {
userService.updateUserMobile(getLoginUserId(), reqVO);
return success(true);
}
@GetMapping("/get-info")
@Operation(summary = "获得基本信息")
@PreAuthenticated
public CommonResult<AppUserQueryVo> getUserBaseInfo() {
return success(userService.getAppUser(getLoginUserId()));
}
@GetMapping("/getBill")
@Operation(summary = "获取用户账单")
@PreAuthenticated
@Parameters({
@Parameter(name = "cate", description = "状态,0余额 1-积分",
required = true, example = "1"),
@Parameter(name = "type", description = "状态,0全部 1消费 2充值 3退款",
required = true, example = "1"),
@Parameter(name = "page", description = "页码,默认为1",
required = true, example = "1"),
@Parameter(name = "pagesize", description = "页大小,默认为10",
required = true, example = "10 ")
})
public CommonResult<List<AppUserBillVO>> getUserBill(@RequestParam(value = "cate", defaultValue = "0") int cate,
@RequestParam(value = "type", defaultValue = "0") int type,
@RequestParam(value = "page", defaultValue = "1") int page,
@RequestParam(value = "pagesize", defaultValue = "10") int pagesize) {
return success(userBillService.getBillList(getLoginUserId(),cate,type,page,pagesize));
}
}
@@ -0,0 +1,71 @@
package co.yixiang.yshop.module.member.controller.app.user.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 用户账单 vo
*
* @author yshop
*/
@Data
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AppUserBillVO {
/**
* 用户账单id
*/
private Long id;
/**
* 用户uid
*/
private Long uid;
/**
* 关联id
*/
private String linkId;
/**
* 0 = 支出 1 = 获得
*/
private Integer pm;
/**
* 账单标题
*/
private String title;
/**
* 明细种类
*/
private String category;
/**
* 明细类型
*/
private String type;
/**
* 明细数字
*/
private BigDecimal number;
/**
* 剩余
*/
private BigDecimal balance;
/**
* 备注
*/
private String mark;
/**
* 0 = 带确定 1 = 有效 -1 = 无效
*/
private Integer status;
private LocalDateTime createTime;
}
@@ -0,0 +1,28 @@
package co.yixiang.yshop.module.member.controller.app.user.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "用户 APP - 用户个人信息 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AppUserInfoRespVO {
@Schema(description = "用户id", required = true, example = "yshop")
private Long id;
@Schema(description = "用户昵称", required = true, example = "yshop")
private String nickname;
@Schema(description = "用户头像", required = true, example = "/infra/file/get/35a12e57-4297-4faa-bf7d-7ed2f211c952")
private String avatar;
@Schema(description = "用户手机号", required = true, example = "15601691300")
private String mobile;
@Schema(description = "生日", required = true, example = "2023-10-11")
private String birthday;
}
@@ -0,0 +1,48 @@
package co.yixiang.yshop.module.member.controller.app.user.vo;
import jakarta.validation.constraints.NotBlank;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 用户信息 vo
*
* @author yshop
*/
@Data
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AppUserNickVO {
/**
* 用户nickname
*/
@NotBlank(message = "用户昵称不能为空")
private String nickname;
/**
* 用户birthday
*/
@NotBlank(message = "生日不能为空")
private String birthday;
/**
* avatar
*/
@NotBlank(message = "用户头像不能为空")
private String avatar;
/**
* gender
*/
private Integer gender;
/**
* mobile
*/
private String mobile;
}
@@ -0,0 +1,52 @@
package co.yixiang.yshop.module.member.controller.app.user.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.io.Serializable;
/**
* @ClassName OrderCountDTO
* @Author hupeng <610796224@qq.com>
* @Date 2023/6/18
**/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "用户 APP - 用户 OrderCountDTO")
public class AppUserOrderCountVo implements Serializable {
/**订单支付没有退款 数量*/
@Schema(description = "订单支付没有退款数量", required = true)
private Long orderCount;
/**订单支付没有退款 支付总金额*/
@Schema(description = "订单支付没有退款支付总金额", required = true)
private Double sumPrice;
/**订单待支付 数量*/
@Schema(description = "订单待支付数量", required = true)
private Long unpaidCount;
/**订单待发货数量*/
@Schema(description = "订单待发货数量", required = true)
private Long unshippedCount;
/**订单待收货数量*/
@Schema(description = "订单待收货数量", required = true)
private Long receivedCount;
/**订单待评价数量*/
@Schema(description = "订单待评价数量", required = true)
private Long evaluatedCount;
/**订单已完成数量*/
@Schema(description = "订单已完成数量", required = true)
private Long completeCount;
/**订单退款数量*/
@Schema(description = "订单退款数量", required = true)
private Long refundCount;
}
@@ -0,0 +1,113 @@
package co.yixiang.yshop.module.member.controller.app.user.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
* <p>
* 用户表 查询结果对象
* </p>
*
* @author hupeng
* @date 2023-6-18
*/
@Data
@Schema(description = "用户 APP - 用户表查 Response VO")
public class AppUserQueryVo implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "用户id", required = true)
private Long id;
@Schema(description = "用户账户(跟accout一样)\"", required = true)
private String username;
@Schema(description = "用户账号", required = true)
private String account;
@Schema(description = "优惠券数量", required = true)
private Long couponCount = 0L;
@Schema(description = "订单详情数据", required = true)
private AppUserOrderCountVo orderStatusNum;
private Integer statu;
@Schema(description = "总的签到天数", required = true)
private Long sumSignDay;
@Schema(description = "当天是否签到", required = true)
private Integer isDaySign;
@Schema(description = "昨天是否签到", required = true)
private Integer isYesterDaySign;
@Schema(description = "核销权限", required = true)
private Integer checkStatus;
@Schema(description = "用户昵称", required = true)
private String nickname;
@Schema(description = "用户头像", required = true)
private String avatar;
@Schema(description = "手机号码", required = true)
private String mobile;
@Schema(description = "用户余额", required = true)
private BigDecimal nowMoney;
@Schema(description = "用户花费的金额", required = true)
private BigDecimal sumMoney;
@Schema(description = "佣金金额", required = true)
private BigDecimal brokeragePrice;
@Schema(description = "用户剩余积分", required = true)
private BigDecimal integral;
@Schema(description = "连续签到天数", required = true)
private Integer signNum;
@Schema(description = "推广元id", required = true)
private Long spreadUid;
@Schema(description = "用户类型", required = true)
private String userType;
@Schema(description = "是否为推广员", required = true)
private Integer isPromoter;
@Schema(description = "用户购买次数", required = true)
private Integer payCount;
@Schema(description = "下级人数", required = true)
private Integer spreadCount;
@Schema(description = "详细地址", required = true)
private String addres;
private Integer gender;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
private String birthday;
@Schema(description = "用户登陆类型,h5,wechat,routine", required = true)
private String loginType;
}
@@ -0,0 +1,24 @@
package co.yixiang.yshop.module.member.controller.app.user.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.*;
/**
* 用户充值 vo
*
* @author yshop
*/
@Data
public class AppUserRechargeVO {
@Schema(description = "充值ID", required = true)
@NotBlank(message = "请选择要充值的面值")
private String rechargeId;
}
@@ -0,0 +1,48 @@
package co.yixiang.yshop.module.member.controller.app.user.vo;
import co.yixiang.yshop.framework.common.validation.Mobile;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
@Schema(description = "用户 APP - 修改手机 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppUserUpdateMobileReqVO {
@Schema(description = "手机验证码", required = true, example = "1024")
@NotEmpty(message = "手机验证码不能为空")
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
private String code;
@Schema(description = "手机号",required = true,example = "15823654487")
@NotBlank(message = "手机号不能为空")
@Length(min = 8, max = 11, message = "手机号码长度为 8-11 位")
@Mobile
private String mobile;
@Schema(description = "原手机验证码", required = true, example = "1024")
@NotEmpty(message = "原手机验证码不能为空")
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
private String oldCode;
// TODO @yshop:oldMobile 应该不用传递
@Schema(description = "原手机号",required = true,example = "15823654487")
@NotBlank(message = "手机号不能为空")
@Length(min = 8, max = 11, message = "手机号码长度为 8-11 位")
@Mobile
private String oldMobile;
}
@@ -0,0 +1,34 @@
package co.yixiang.yshop.module.member.controller.app.weixin;
import co.yixiang.yshop.framework.common.pojo.CommonResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static co.yixiang.yshop.framework.common.pojo.CommonResult.success;
@Tag(name = "微信公众号")
@RestController
@RequestMapping("/member/wx-mp")
@Validated
@Slf4j
public class AppWxMpController {
@Resource
private WxMpService mpService;
@GetMapping("/create-jsapi-signature")
@Operation(summary = "创建微信 JS SDK 初始化所需的签名",
description = "参考 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 文档")
public CommonResult<WxJsapiSignature> createJsapiSignature(@RequestParam("url") String url) throws WxErrorException {
return success(mpService.createJsapiSignature(url));
}
}
@@ -0,0 +1,28 @@
package co.yixiang.yshop.module.member.convert.auth;
import co.yixiang.yshop.module.member.controller.app.auth.vo.*;
import co.yixiang.yshop.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
import co.yixiang.yshop.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;
import co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import co.yixiang.yshop.module.system.api.social.dto.SocialUserBindReqDTO;
import co.yixiang.yshop.module.system.api.social.dto.SocialUserUnbindReqDTO;
import co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface AuthConvert {
AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class);
SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialLoginReqVO reqVO);
SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO);
SmsCodeSendReqDTO convert(AppAuthSmsSendReqVO reqVO);
SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp);
SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, Integer scene, String usedIp);
AppAuthLoginRespVO convert(OAuth2AccessTokenRespDTO bean);
}
@@ -0,0 +1,6 @@
/**
* 提供 POJO 类的实体转换
*
* 目前使用 MapStruct 框架
*/
package co.yixiang.yshop.module.member.convert;
@@ -0,0 +1,19 @@
package co.yixiang.yshop.module.member.convert.social;
import co.yixiang.yshop.module.member.controller.app.social.vo.AppSocialUserBindReqVO;
import co.yixiang.yshop.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
import co.yixiang.yshop.module.system.api.social.dto.SocialUserBindReqDTO;
import co.yixiang.yshop.module.system.api.social.dto.SocialUserUnbindReqDTO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface SocialUserConvert {
SocialUserConvert INSTANCE = Mappers.getMapper(SocialUserConvert.class);
SocialUserBindReqDTO convert(Long userId, Integer userType, AppSocialUserBindReqVO reqVO);
SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO);
}
@@ -0,0 +1,41 @@
package co.yixiang.yshop.module.member.convert.user;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import co.yixiang.yshop.module.member.api.user.dto.MemberUserRespDTO;
import co.yixiang.yshop.module.member.controller.admin.user.vo.UserCreateReqVO;
import co.yixiang.yshop.module.member.controller.admin.user.vo.UserRespVO;
import co.yixiang.yshop.module.member.controller.admin.user.vo.UserUpdateReqVO;
import co.yixiang.yshop.module.member.controller.app.user.vo.AppUserInfoRespVO;
import co.yixiang.yshop.module.member.controller.app.user.vo.AppUserQueryVo;
import co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface UserConvert {
UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);
AppUserInfoRespVO convert(MemberUserDO bean);
MemberUserRespDTO convert2(MemberUserDO bean);
AppUserQueryVo convert3(MemberUserDO bean);
UserRespVO convert4(MemberUserDO bean);
List<MemberUserRespDTO> convertList2(List<MemberUserDO> list);
MemberUserDO convert(UserCreateReqVO bean);
MemberUserDO convert(UserUpdateReqVO bean);
UserRespVO convert(MemberUserDO bean,Boolean bool);
List<UserRespVO> convertList(List<MemberUserDO> list);
PageResult<UserRespVO> convertPage(PageResult<MemberUserDO> page);
}
@@ -0,0 +1,17 @@
package co.yixiang.yshop.module.member.convert.useraddress;
import co.yixiang.yshop.framework.ip.core.Area;
import co.yixiang.yshop.module.member.controller.app.address.vo.AreaNodeRespVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface AreaConvert {
AreaConvert INSTANCE = Mappers.getMapper(AreaConvert.class);
List<AreaNodeRespVO> convertList(List<Area> list);
}
@@ -0,0 +1,35 @@
package co.yixiang.yshop.module.member.convert.useraddress;
import java.util.*;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import co.yixiang.yshop.module.member.controller.app.address.vo.AppUserAddressQueryVo;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import co.yixiang.yshop.module.member.controller.admin.useraddress.vo.*;
import co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;
/**
* 用户地址 Convert
*
* @author yshop
*/
@Mapper
public interface UserAddressConvert {
UserAddressConvert INSTANCE = Mappers.getMapper(UserAddressConvert.class);
UserAddressDO convert(UserAddressCreateReqVO bean);
UserAddressDO convert(UserAddressUpdateReqVO bean);
UserAddressRespVO convert(UserAddressDO bean);
List<UserAddressRespVO> convertList(List<UserAddressDO> list);
List<AppUserAddressQueryVo> convertList02(List<UserAddressDO> list);
PageResult<UserAddressRespVO> convertPage(PageResult<UserAddressDO> page);
}
@@ -0,0 +1,36 @@
package co.yixiang.yshop.module.member.convert.userbill;
import java.util.*;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import co.yixiang.yshop.module.member.controller.app.user.vo.AppUserBillVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import co.yixiang.yshop.module.member.controller.admin.userbill.vo.*;
import co.yixiang.yshop.module.member.dal.dataobject.userbill.UserBillDO;
/**
* 用户账单 Convert
*
* @author yshop
*/
@Mapper
public interface UserBillConvert {
UserBillConvert INSTANCE = Mappers.getMapper(UserBillConvert.class);
UserBillDO convert(UserBillCreateReqVO bean);
UserBillDO convert(UserBillUpdateReqVO bean);
UserBillRespVO convert(UserBillDO bean);
List<UserBillRespVO> convertList(List<UserBillDO> list);
List<AppUserBillVO> convertList02(List<UserBillDO> list);
PageResult<UserBillRespVO> convertPage(PageResult<UserBillDO> page);
}
@@ -0,0 +1,187 @@
package co.yixiang.yshop.module.member.dal.dataobject.user;
import co.yixiang.yshop.framework.common.enums.CommonStatusEnum;
import co.yixiang.yshop.framework.tenant.core.db.TenantBaseDO;
import co.yixiang.yshop.module.member.api.user.dto.WechatUserDto;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import lombok.*;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 会员用户 DO
*
* uk_mobile 索引:基于 {@link #mobile} 字段
*
* @author yshop
*/
@TableName(value = "yshop_user", autoResultMap = true)
@KeySequence("member_user_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MemberUserDO extends TenantBaseDO {
/**
* 用户ID
*/
@TableId
private Long id;
/**
* 用户昵称
*/
private String nickname;
/**
* 用户头像
*/
private String avatar;
/**
* 帐号状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 手机
*/
private String mobile;
/**
* 加密后的密码
*
* 因为目前使用 {@link BCryptPasswordEncoder} 加密器,所以无需自己处理 salt 盐
*/
private String password;
/**
* 注册 IP
*/
private String registerIp;
/**
* 最后登录IP
*/
private String loginIp;
/**
* 最后登录时间
*/
private LocalDateTime loginDate;
/**
* 用户账户(跟accout一样)
*/
private String username;
/**
* 真实姓名
*/
private String realName;
/**
* 生日
*/
private String birthday;
/**
* 身份证号码
*/
private String cardId;
/**
* 用户备注
*/
private String mark;
/**
* 合伙人id
*/
private Integer partnerId;
/**
* 用户分组id
*/
private Integer groupId;
/**
* 添加ip
*/
private String addIp;
/**
* 最后一次登录ip
*/
private String lastIp;
/**
* 用户余额
*/
private BigDecimal nowMoney;
/**
* 佣金金额
*/
private BigDecimal brokeragePrice;
/**
* 用户剩余积分
*/
private BigDecimal integral;
/**
* 连续签到天数
*/
private Integer signNum;
/**
* 等级
*/
private Integer level;
/**
* 推广元id
*/
private Long spreadUid;
/**
* 推广员关联时间
*/
private LocalDateTime spreadTime;
/**
* 用户类型
*/
private String userType;
/**
* 是否为推广员
*/
private Integer isPromoter;
/**
* 用户购买次数
*/
private Integer payCount;
/**
* 下级人数
*/
private Integer spreadCount;
/**
* 详细地址
*/
private String addres;
/**
* 管理员编号
*/
private Integer adminid;
/**
* 用户登陆类型,h5,wechat,routine
*/
private String loginType;
//公众号openid
private String openid;
//小程序openid
private String routineOpenid;
//性别
private Integer gender;
/** 微信用户json信息 */
@TableField(typeHandler = FastjsonTypeHandler.class)
@Deprecated
private WechatUserDto wxProfile;
}
@@ -0,0 +1,84 @@
package co.yixiang.yshop.module.member.dal.dataobject.useraddress;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;
/**
* 用户地址 DO
*
* @author yshop
*/
@TableName("yshop_user_address")
@KeySequence("yshop_user_address_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserAddressDO extends BaseDO {
/**
* 用户地址id
*/
@TableId
private Long id;
/**
* 用户id
*/
private Long uid;
/**
* 收货人姓名
*/
private String realName;
/**
* 收货人电话
*/
private String phone;
/**
* 收货人所在省
*/
private String province;
/**
* 收货人所在市
*/
private String city;
/**
* 城市id
*/
private Integer cityId;
/**
* 收货人所在区
*/
private String district;
/**
* 收货人详细地址
*/
private String detail;
/**
* 邮编
*/
private String postCode;
/**
* 经度
*/
private String longitude;
/**
* 纬度
*/
private String latitude;
/**
* 是否默认
*/
private Integer isDefault;
/**
* 地址
*/
private String address;
}
@@ -0,0 +1,78 @@
package co.yixiang.yshop.module.member.dal.dataobject.userbill;
import lombok.*;
import java.util.*;
import java.math.BigDecimal;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;
/**
* 用户账单 DO
*
* @author yshop
*/
@TableName("yshop_user_bill")
@KeySequence("yshop_user_bill_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserBillDO extends BaseDO {
/**
* 用户账单id
*/
@TableId
private Long id;
/**
* 用户uid
*/
private Long uid;
/**
* 关联id
*/
private String linkId;
/**
* 0 = 支出 1 = 获得
*/
private Integer pm;
/**
* 账单标题
*/
private String title;
/**
* 明细种类
*/
private String category;
/**
* 明细类型
*/
private String type;
/**
* 明细数字
*/
private BigDecimal number;
/**
* 剩余
*/
private BigDecimal balance;
/**
* 备注
*/
private String mark;
/**
* 0 = 带确定 1 = 有效 -1 = 无效
*/
private Integer status;
@TableField(exist = false)
private BigDecimal sumAll;
private String extendField;
}
@@ -0,0 +1,70 @@
package co.yixiang.yshop.module.member.dal.mysql.user;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;
import co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;
import co.yixiang.yshop.module.member.controller.admin.user.vo.UserExportReqVO;
import co.yixiang.yshop.module.member.controller.admin.user.vo.UserPageReqVO;
import co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import java.math.BigDecimal;
import java.util.List;
/**
* 会员 User Mapper
*
* @author yshop
*/
@Mapper
public interface MemberUserMapper extends BaseMapperX<MemberUserDO> {
default MemberUserDO selectByMobile(String mobile) {
return selectOne(MemberUserDO::getMobile, mobile);
}
default List<MemberUserDO> selectListByNicknameLike(String nickname) {
return selectList(new LambdaQueryWrapperX<MemberUserDO>()
.likeIfPresent(MemberUserDO::getNickname, nickname));
}
default PageResult<MemberUserDO> selectPage(UserPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<MemberUserDO>()
.likeIfPresent(MemberUserDO::getLoginType, reqVO.getLoginType())
.likeIfPresent(MemberUserDO::getUsername, reqVO.getUsername())
.likeIfPresent(MemberUserDO::getRealName, reqVO.getRealName())
.likeIfPresent(MemberUserDO::getNickname, reqVO.getNickname())
.eqIfPresent(MemberUserDO::getMobile, reqVO.getMobile())
.betweenIfPresent(MemberUserDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(MemberUserDO::getId));
}
default List<MemberUserDO> selectList(UserExportReqVO reqVO) {
return selectList(new LambdaQueryWrapperX<MemberUserDO>()
.likeIfPresent(MemberUserDO::getUsername, reqVO.getUsername())
.likeIfPresent(MemberUserDO::getRealName, reqVO.getRealName())
.likeIfPresent(MemberUserDO::getNickname, reqVO.getNickname())
.eqIfPresent(MemberUserDO::getMobile, reqVO.getPhone())
.betweenIfPresent(MemberUserDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(MemberUserDO::getId));
}
@Update("update yshop_user set now_money=now_money-#{payPrice}" +
" where id=#{uid}")
int decPrice(@Param("payPrice") BigDecimal payPrice, @Param("uid") Long uid);
@Update("update yshop_user set integral=integral-#{score}" +
" where id=#{uid}")
int decScore(@Param("score") Integer score, @Param("uid") Long uid);
@Update("update yshop_user set pay_count=pay_count+1" +
" where id=#{uid}")
int incPayCount(@Param("uid") Long uid);
@Update("update yshop_user set now_money=now_money+#{price}" +
" where id=#{uid}")
int incMoney(@Param("uid") Long uid,@Param("price") BigDecimal price);
}
@@ -0,0 +1,56 @@
package co.yixiang.yshop.module.member.dal.mysql.useraddress;
import java.util.*;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;
import co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;
import co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;
import org.apache.ibatis.annotations.Mapper;
import co.yixiang.yshop.module.member.controller.admin.useraddress.vo.*;
/**
* 用户地址 Mapper
*
* @author yshop
*/
@Mapper
public interface UserAddressMapper extends BaseMapperX<UserAddressDO> {
default PageResult<UserAddressDO> selectPage(UserAddressPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<UserAddressDO>()
.eqIfPresent(UserAddressDO::getUid, reqVO.getUid())
.likeIfPresent(UserAddressDO::getRealName, reqVO.getRealName())
.eqIfPresent(UserAddressDO::getPhone, reqVO.getPhone())
.eqIfPresent(UserAddressDO::getProvince, reqVO.getProvince())
.eqIfPresent(UserAddressDO::getCity, reqVO.getCity())
.eqIfPresent(UserAddressDO::getCityId, reqVO.getCityId())
.eqIfPresent(UserAddressDO::getDistrict, reqVO.getDistrict())
.eqIfPresent(UserAddressDO::getDetail, reqVO.getDetail())
.eqIfPresent(UserAddressDO::getPostCode, reqVO.getPostCode())
.eqIfPresent(UserAddressDO::getLongitude, reqVO.getLongitude())
.eqIfPresent(UserAddressDO::getLatitude, reqVO.getLatitude())
.eqIfPresent(UserAddressDO::getIsDefault, reqVO.getIsDefault())
.betweenIfPresent(UserAddressDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(UserAddressDO::getId));
}
default List<UserAddressDO> selectList(UserAddressExportReqVO reqVO) {
return selectList(new LambdaQueryWrapperX<UserAddressDO>()
.eqIfPresent(UserAddressDO::getUid, reqVO.getUid())
.likeIfPresent(UserAddressDO::getRealName, reqVO.getRealName())
.eqIfPresent(UserAddressDO::getPhone, reqVO.getPhone())
.eqIfPresent(UserAddressDO::getProvince, reqVO.getProvince())
.eqIfPresent(UserAddressDO::getCity, reqVO.getCity())
.eqIfPresent(UserAddressDO::getCityId, reqVO.getCityId())
.eqIfPresent(UserAddressDO::getDistrict, reqVO.getDistrict())
.eqIfPresent(UserAddressDO::getDetail, reqVO.getDetail())
.eqIfPresent(UserAddressDO::getPostCode, reqVO.getPostCode())
.eqIfPresent(UserAddressDO::getLongitude, reqVO.getLongitude())
.eqIfPresent(UserAddressDO::getLatitude, reqVO.getLatitude())
.eqIfPresent(UserAddressDO::getIsDefault, reqVO.getIsDefault())
.betweenIfPresent(UserAddressDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(UserAddressDO::getId));
}
}
@@ -0,0 +1,52 @@
package co.yixiang.yshop.module.member.dal.mysql.userbill;
import java.util.*;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;
import co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;
import co.yixiang.yshop.module.member.dal.dataobject.userbill.UserBillDO;
import org.apache.ibatis.annotations.Mapper;
import co.yixiang.yshop.module.member.controller.admin.userbill.vo.*;
/**
* 用户账单 Mapper
*
* @author yshop
*/
@Mapper
public interface UserBillMapper extends BaseMapperX<UserBillDO> {
default PageResult<UserBillDO> selectPage(UserBillPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<UserBillDO>()
.eqIfPresent(UserBillDO::getUid, reqVO.getUid())
.eqIfPresent(UserBillDO::getLinkId, reqVO.getLinkId())
.eqIfPresent(UserBillDO::getPm, reqVO.getPm())
.eqIfPresent(UserBillDO::getTitle, reqVO.getTitle())
.eqIfPresent(UserBillDO::getCategory, reqVO.getCategory())
.eqIfPresent(UserBillDO::getType, reqVO.getType())
.eqIfPresent(UserBillDO::getNumber, reqVO.getNumber())
.eqIfPresent(UserBillDO::getBalance, reqVO.getBalance())
.eqIfPresent(UserBillDO::getMark, reqVO.getMark())
.betweenIfPresent(UserBillDO::getCreateTime, reqVO.getCreateTime())
.eqIfPresent(UserBillDO::getStatus, reqVO.getStatus())
.orderByDesc(UserBillDO::getId));
}
default List<UserBillDO> selectList(UserBillExportReqVO reqVO) {
return selectList(new LambdaQueryWrapperX<UserBillDO>()
.eqIfPresent(UserBillDO::getUid, reqVO.getUid())
.eqIfPresent(UserBillDO::getLinkId, reqVO.getLinkId())
.eqIfPresent(UserBillDO::getPm, reqVO.getPm())
.eqIfPresent(UserBillDO::getTitle, reqVO.getTitle())
.eqIfPresent(UserBillDO::getCategory, reqVO.getCategory())
.eqIfPresent(UserBillDO::getType, reqVO.getType())
.eqIfPresent(UserBillDO::getNumber, reqVO.getNumber())
.eqIfPresent(UserBillDO::getBalance, reqVO.getBalance())
.eqIfPresent(UserBillDO::getMark, reqVO.getMark())
.betweenIfPresent(UserBillDO::getCreateTime, reqVO.getCreateTime())
.eqIfPresent(UserBillDO::getStatus, reqVO.getStatus())
.orderByDesc(UserBillDO::getId));
}
}
@@ -0,0 +1,24 @@
package co.yixiang.yshop.module.member.dal.redis;
import co.yixiang.yshop.framework.redis.core.RedisKeyDefine;
import static co.yixiang.yshop.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
/**
* System Redis Key 枚举类
*
* @author yshop
*/
public interface RedisKeyConstants {
RedisKeyDefine YSHOP_MINI_LOGIN_CACHE_KEY = new RedisKeyDefine("小程序登录session",
"yshop_mini_login_cache:%s", // 参数为访问uid+key
STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.FOREVER);
}
@@ -0,0 +1,44 @@
package co.yixiang.yshop.module.member.dal.redis.order;
import co.yixiang.yshop.framework.common.util.json.JsonUtils;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;
import static co.yixiang.yshop.module.member.dal.redis.RedisKeyConstants.YSHOP_MINI_LOGIN_CACHE_KEY;
/**
*
* @author yshop
*/
@Repository
public class MiniRedisDAO {
@Resource
private StringRedisTemplate stringRedisTemplate;
public String get(String key) {
String redisKey = formatKey(key);
return stringRedisTemplate.opsForValue().get(redisKey);
}
public String set(String str,String key) {
String redisKey = formatKey(key);
stringRedisTemplate.opsForValue().set(redisKey, str);
return key;
}
public void delete(String key) {
String redisKey = formatKey(key);
stringRedisTemplate.delete(redisKey);
}
private static String formatKey(String key) {
return String.format(YSHOP_MINI_LOGIN_CACHE_KEY.getKeyTemplate(), key);
}
}
@@ -0,0 +1,6 @@
/**
* 属于 member 模块的 framework 封装
*
* @author yshop
*/
package co.yixiang.yshop.module.member.framework;
@@ -0,0 +1,24 @@
package co.yixiang.yshop.module.member.framework.web.config;
import co.yixiang.yshop.framework.swagger.config.YshopSwaggerAutoConfiguration;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* member 模块的 web 组件的 Configuration
*
* @author yshop
*/
@Configuration(proxyBeanMethods = false)
public class MemberWebConfiguration {
/**
* member 模块的 API 分组
*/
@Bean
public GroupedOpenApi memberGroupedOpenApi() {
return YshopSwaggerAutoConfiguration.buildGroupedOpenApi("member");
}
}
@@ -0,0 +1,4 @@
/**
* member 模块的 web 配置
*/
package co.yixiang.yshop.module.member.framework.web;
@@ -0,0 +1,113 @@
package co.yixiang.yshop.module.member.service.auth;
import co.yixiang.yshop.module.member.controller.app.auth.vo.*;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 会员的认证 Service 接口
*
* 提供用户的账号密码登录、token 的校验等认证相关的功能
*
* @author yshop
*/
public interface MemberAuthService {
/**
* 手机 + 密码登录
*
* @param reqVO 登录信息
* @return 登录结果
*/
AppAuthLoginRespVO login(@Valid AppAuthLoginReqVO reqVO);
/**
* 基于 token 退出登录
*
* @param token token
*/
void logout(String token);
/**
* 手机 + 验证码登陆
*
* @param reqVO 登陆信息
* @return 登录结果
*/
AppAuthLoginRespVO smsLogin(@Valid AppAuthSmsLoginReqVO reqVO);
/**
* 社交登录,使用 code 授权码
*
* @param reqVO 登录信息
* @return 登录结果
*/
//AppAuthLoginRespVO socialLogin(@Valid AppAuthSocialLoginReqVO reqVO);
/**
* 微信小程序的一键登录
*
* @param reqVO 登录信息
* @return 登录结果
*/
AppAuthLoginRespVO weixinMiniAppLogin(AppAuthWeixinMiniAppLoginReqVO reqVO);
/**
* 微信小程序的一键登录
*
* @param loginVO 登录信息
* @return 登录结果
*/
AppAuthLoginRespVO weixinMiniAppLogin2(AppWeixinMiniLoginVO loginVO);
/**
* 微信小程序的一键登录
*
* @param encryptedData encryptedData
* @param encryptedData encryptedData
* @param encryptedData encryptedData
* @return 登录结果
*/
AppAuthLoginRespVO weixinMiniAppLogin3(String encryptedData, String iv, String openid);
/**
* 获得社交认证 URL
*
* @param type 社交平台类型
* @param redirectUri 跳转地址
* @return 认证 URL
*/
//String getSocialAuthorizeUrl(Integer type, String redirectUri);
/**
* 修改用户密码
* @param userId 用户id
* @param userReqVO 用户请求实体类
*/
void updatePassword(Long userId, AppAuthUpdatePasswordReqVO userReqVO);
/**
* 忘记密码
* @param userReqVO 用户请求实体类
*/
//void resetPassword(AppAuthResetPasswordReqVO userReqVO);
/**
* 给用户发送短信验证码
*
* @param userId 用户编号
* @param reqVO 发送信息
*/
void sendSmsCode(Long userId, AppAuthSmsSendReqVO reqVO);
/**
* 刷新访问令牌
*
* @param refreshToken 刷新令牌
* @return 登录结果
*/
AppAuthLoginRespVO refreshToken(String refreshToken);
}
@@ -0,0 +1,430 @@
package co.yixiang.yshop.module.member.service.auth;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import co.yixiang.yshop.framework.common.enums.CommonStatusEnum;
import co.yixiang.yshop.framework.common.enums.UserTypeEnum;
import co.yixiang.yshop.framework.common.util.monitor.TracerUtils;
import co.yixiang.yshop.framework.common.util.servlet.ServletUtils;
import co.yixiang.yshop.module.member.controller.app.auth.vo.*;
import co.yixiang.yshop.module.member.convert.auth.AuthConvert;
import co.yixiang.yshop.module.member.convert.user.UserConvert;
import co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;
import co.yixiang.yshop.module.member.dal.mysql.user.MemberUserMapper;
import co.yixiang.yshop.module.member.dal.redis.order.MiniRedisDAO;
import co.yixiang.yshop.module.member.enums.LoginTypeEnum;
import co.yixiang.yshop.module.member.service.user.MemberUserService;
import co.yixiang.yshop.module.system.api.logger.LoginLogApi;
import co.yixiang.yshop.module.system.api.logger.dto.LoginLogCreateReqDTO;
import co.yixiang.yshop.module.system.api.oauth2.OAuth2TokenApi;
import co.yixiang.yshop.module.system.api.oauth2.dto.OAuth2AccessTokenCreateReqDTO;
import co.yixiang.yshop.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;
import co.yixiang.yshop.module.system.api.sms.SmsCodeApi;
import co.yixiang.yshop.module.system.api.social.SocialUserApi;
import co.yixiang.yshop.module.system.api.social.dto.SocialUserBindReqDTO;
import co.yixiang.yshop.module.system.enums.logger.LoginLogTypeEnum;
import co.yixiang.yshop.module.system.enums.logger.LoginResultEnum;
import co.yixiang.yshop.module.system.enums.oauth2.OAuth2ClientConstants;
import co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;
import co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Objects;
import static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;
import static co.yixiang.yshop.framework.common.util.servlet.ServletUtils.getClientIP;
import static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.*;
/**
* 会员的认证 Service 接口
*
* @author yshop
*/
@Service
@Slf4j
public class MemberAuthServiceImpl implements MemberAuthService {
@Resource
private MemberUserService userService;
@Resource
private SmsCodeApi smsCodeApi;
@Resource
private LoginLogApi loginLogApi;
@Resource
private SocialUserApi socialUserApi;
@Resource
private OAuth2TokenApi oauth2TokenApi;
@Resource
private WxMaService wxMaService;
@Resource
private WxMpService mpService;
@Resource
private PasswordEncoder passwordEncoder;
@Resource
private MemberUserMapper userMapper;
@Resource
private MiniRedisDAO miniRedisDAO;
@Override
public AppAuthLoginRespVO login(AppAuthLoginReqVO reqVO) {
// 使用手机 + 密码,进行登录。
MemberUserDO user = login0(reqVO.getMobile(), reqVO.getPassword());
// 如果 socialType 非空,说明需要绑定社交用户
if (reqVO.getSocialType() != null) {
socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
}
// 创建 Token 令牌,记录登录日志
return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE);
}
@Override
@Transactional
public AppAuthLoginRespVO smsLogin(AppAuthSmsLoginReqVO reqVO) {
// 校验验证码
String userIp = getClientIP();
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_LOGIN.getScene(), userIp));
MemberUserDO user = null;
if(StrUtil.isNotBlank(reqVO.getOpenid())){
LambdaQueryWrapper<MemberUserDO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
//根据openid查用户是否存在
if(LoginTypeEnum.ROUNTINE.getValue().equals(reqVO.getFrom())){
lambdaQueryWrapper.eq(MemberUserDO::getRoutineOpenid,reqVO.getOpenid());
}else {
lambdaQueryWrapper.eq(MemberUserDO::getOpenid,reqVO.getOpenid());
}
user = userMapper.selectOne(lambdaQueryWrapper);
}
if(user == null){
// 获得获得注册用户
user = userService.createUserIfAbsent(reqVO.getMobile(), userIp,reqVO.getFrom());
Assert.notNull(user, "获取用户失败,结果为空");
if(StrUtil.isNotBlank(reqVO.getOpenid())){
if(LoginTypeEnum.ROUNTINE.getValue().equals(reqVO.getFrom())){
user.setRoutineOpenid(reqVO.getOpenid());
}else {
user.setOpenid(reqVO.getOpenid());
}
userService.updateById(user);
}
}else {
if(StrUtil.isBlank(user.getMobile())){
user.setMobile(reqVO.getMobile());
userService.updateById(user);
}
}
// 如果 socialType 非空,说明需要绑定社交用户
if (reqVO.getSocialType() != null) {
socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
}
// 创建 Token 令牌,记录登录日志
AppAuthLoginRespVO appAuthLoginRespVO = createTokenAfterLoginSuccess(user, reqVO.getMobile(),
LoginLogTypeEnum.LOGIN_SMS);
appAuthLoginRespVO.setUserInfo(UserConvert.INSTANCE.convert3(user));
return appAuthLoginRespVO;
// 创建 Token 令牌,记录登录日志
// return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS);
}
// @Override
// public AppAuthLoginRespVO socialLogin(AppAuthSocialLoginReqVO reqVO) {
// // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
// Long userId = socialUserApi.getBindUserId(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),
// reqVO.getCode(), reqVO.getState());
// if (userId == null) {
// throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
// }
//
// // 自动登录
// MemberUserDO user = userService.getUser(userId);
// if (user == null) {
// throw exception(USER_NOT_EXISTS);
// }
//
// // 创建 Token 令牌,记录登录日志
// return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL);
// }
@Override
public AppAuthLoginRespVO weixinMiniAppLogin(AppAuthWeixinMiniAppLoginReqVO reqVO) {
// 获得对应的手机号信息
WxMaPhoneNumberInfo phoneNumberInfo;
try {
phoneNumberInfo = wxMaService.getUserService().getNewPhoneNoInfo(reqVO.getPhoneCode());
} catch (Exception exception) {
throw exception(AUTH_WEIXIN_MINI_APP_PHONE_CODE_ERROR);
}
// 获得获得注册用户
MemberUserDO user = userService.createUserIfAbsent(phoneNumberInfo.getPurePhoneNumber(), getClientIP(),
LoginTypeEnum.ROUNTINE.getValue());
Assert.notNull(user, "获取用户失败,结果为空");
// 绑定社交用户
socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
SocialTypeEnum.WECHAT_MINI_APP.getType(), reqVO.getLoginCode(), ""));
// 创建 Token 令牌,记录登录日志
return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL);
}
/**
* 微信小程序的一键登录
*
* @param loginVO 登录信息
* @return 登录结果
*/
@Override
public AppAuthLoginRespVO weixinMiniAppLogin2(AppWeixinMiniLoginVO loginVO){
try {
WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(loginVO.getCode());
log.info(session.getOpenid());
miniRedisDAO.set(session.getSessionKey(),session.getOpenid());
//根据openid查用户是否存在
MemberUserDO memberUserDO = userMapper.selectOne(new LambdaQueryWrapper<MemberUserDO>()
.eq(MemberUserDO::getRoutineOpenid,session.getOpenid()));
AppAuthLoginRespVO appAuthLoginRespVO = new AppAuthLoginRespVO();
if(memberUserDO != null) {
appAuthLoginRespVO = createTokenAfterLoginSuccess(memberUserDO, memberUserDO.getMobile(),
LoginLogTypeEnum.LOGIN_SOCIAL);
appAuthLoginRespVO.setUserInfo(UserConvert.INSTANCE.convert3(memberUserDO));
//return appAuthLoginRespVO;
}
appAuthLoginRespVO.setOpenId(session.getOpenid());
return appAuthLoginRespVO;
} catch (WxErrorException e) {
log.error(e.getMessage());
throw exception(MINI_AUTH_LOGIN_BAD);
}
}
/**
* 微信小程序的一键登录
*
* @param encryptedData encryptedData
* @param encryptedData encryptedData
* @param encryptedData encryptedData
* @return 登录结果
*/
@Override
public AppAuthLoginRespVO weixinMiniAppLogin3(String encryptedData, String iv, String openid){
// 解密用户信息
String sessionKey = miniRedisDAO.get(openid);
if(StrUtil.isBlank(sessionKey)) {
throw exception(MINI_AUTH_LOGIN_BAD2);
}
// 解密
WxMaPhoneNumberInfo phoneNoInfo = wxMaService.getUserService().getPhoneNoInfo(sessionKey, encryptedData, iv);
// 用户已经存在
MemberUserDO memberUserDO = userMapper.selectByMobile(phoneNoInfo.getPhoneNumber());
if (memberUserDO == null) {
// 获得获得注册用户
memberUserDO = userService.createUserIfAbsent(phoneNoInfo.getPhoneNumber(), getClientIP(),
LoginTypeEnum.ROUNTINE.getValue());
memberUserDO.setRoutineOpenid(openid);
memberUserDO.setNickname("yshop用户_" + memberUserDO.getId());
userMapper.updateById(memberUserDO);
}
if(StrUtil.isBlank(memberUserDO.getRoutineOpenid())){
memberUserDO.setRoutineOpenid(openid);
userMapper.updateById(memberUserDO);
}
// 创建 Token 令牌,记录登录日志
AppAuthLoginRespVO appAuthLoginRespVO = createTokenAfterLoginSuccess(memberUserDO, memberUserDO.getMobile(),
LoginLogTypeEnum.LOGIN_SOCIAL);
appAuthLoginRespVO.setOpenId(openid);
appAuthLoginRespVO.setUserInfo(UserConvert.INSTANCE.convert3(memberUserDO));
return appAuthLoginRespVO;
}
private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile, LoginLogTypeEnum logType) {
// 插入登陆日志
createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS);
// 创建 Token 令牌
OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO()
.setUserId(user.getId()).setUserType(getUserType().getValue())
.setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT));
// 构建返回结果
return AuthConvert.INSTANCE.convert(accessTokenRespDTO);
}
// @Override
// public String getSocialAuthorizeUrl(Integer type, String redirectUri) {
// return socialUserApi.getAuthorizeUrl(type, redirectUri);
// }
private MemberUserDO login0(String mobile, String password) {
final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_MOBILE;
// 校验账号是否存在
MemberUserDO user = userService.getUserByMobile(mobile);
if (user == null) {
createLoginLog(null, mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
}
if (!userService.isPasswordMatch(password, user.getPassword())) {
createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
}
// 校验是否禁用
if (ObjectUtil.notEqual(user.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.USER_DISABLED);
throw exception(AUTH_LOGIN_USER_DISABLED);
}
return user;
}
private void createLoginLog(Long userId, String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) {
// 插入登录日志
LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
reqDTO.setLogType(logType.getType());
reqDTO.setTraceId(TracerUtils.getTraceId());
reqDTO.setUserId(userId);
reqDTO.setUserType(getUserType().getValue());
reqDTO.setUsername(mobile);
reqDTO.setUserAgent(ServletUtils.getUserAgent());
reqDTO.setUserIp(getClientIP());
reqDTO.setResult(loginResult.getResult());
loginLogApi.createLoginLog(reqDTO);
// 更新最后登录时间
if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
userService.updateUserLogin(userId, getClientIP());
}
}
@Override
public void logout(String token) {
// 删除访问令牌
OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.removeAccessToken(token);
if (accessTokenRespDTO == null) {
return;
}
// 删除成功,则记录登出日志
createLogoutLog(accessTokenRespDTO.getUserId());
}
@Override
public void updatePassword(Long userId, AppAuthUpdatePasswordReqVO reqVO) {
// 检验旧密码
MemberUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword());
// 更新用户密码
// TODO yshop:需要重构到用户模块
userMapper.updateById(MemberUserDO.builder().id(userDO.getId())
.password(passwordEncoder.encode(reqVO.getPassword())).build());
}
// @Override
// public void resetPassword(AppAuthResetPasswordReqVO reqVO) {
// // 检验用户是否存在
// MemberUserDO userDO = checkUserIfExists(reqVO.getMobile());
//
// // 使用验证码
// smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_FORGET_PASSWORD,
// getClientIP()));
//
// // 更新密码
// userMapper.updateById(MemberUserDO.builder().id(userDO.getId())
// .password(passwordEncoder.encode(reqVO.getPassword())).build());
// }
@Override
public void sendSmsCode(Long userId, AppAuthSmsSendReqVO reqVO) {
// TODO 要根据不同的场景,校验是否有用户
smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP()));
}
@Override
public AppAuthLoginRespVO refreshToken(String refreshToken) {
OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT);
return AuthConvert.INSTANCE.convert(accessTokenDO);
}
/**
* 校验旧密码
*
* @param id 用户 id
* @param oldPassword 旧密码
* @return MemberUserDO 用户实体
*/
@VisibleForTesting
public MemberUserDO checkOldPassword(Long id, String oldPassword) {
MemberUserDO user = userMapper.selectById(id);
if (user == null) {
throw exception(USER_NOT_EXISTS);
}
// 参数:未加密密码,编码后的密码
if (!passwordEncoder.matches(oldPassword,user.getPassword())) {
throw exception(USER_PASSWORD_FAILED);
}
return user;
}
public MemberUserDO checkUserIfExists(String mobile) {
MemberUserDO user = userMapper.selectByMobile(mobile);
if (user == null) {
throw exception(USER_NOT_EXISTS);
}
return user;
}
private void createLogoutLog(Long userId) {
LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType());
reqDTO.setTraceId(TracerUtils.getTraceId());
reqDTO.setUserId(userId);
reqDTO.setUserType(getUserType().getValue());
reqDTO.setUsername(getMobile(userId));
reqDTO.setUserAgent(ServletUtils.getUserAgent());
reqDTO.setUserIp(getClientIP());
reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
loginLogApi.createLoginLog(reqDTO);
}
private String getMobile(Long userId) {
if (userId == null) {
return null;
}
MemberUserDO user = userService.getUser(userId);
return user != null ? user.getMobile() : null;
}
private UserTypeEnum getUserType() {
return UserTypeEnum.MEMBER;
}
}
@@ -0,0 +1,145 @@
package co.yixiang.yshop.module.member.service.user;
import co.yixiang.yshop.framework.common.validation.Mobile;
import co.yixiang.yshop.module.member.controller.app.user.vo.AppUserQueryVo;
import co.yixiang.yshop.module.member.controller.app.user.vo.AppUserUpdateMobileReqVO;
import co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.bind.annotation.RequestParam;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
/**
* 会员用户 Service 接口
*
* @author yshop
*/
public interface MemberUserService extends IService<MemberUserDO> {
/**
* 通过手机查询用户
*
* @param mobile 手机
* @return 用户对象
*/
MemberUserDO getUserByMobile(String mobile);
/**
* 基于用户昵称,模糊匹配用户列表
*
* @param nickname 用户昵称,模糊匹配
* @return 用户信息的列表
*/
List<MemberUserDO> getUserListByNickname(String nickname);
/**
* 基于手机号创建用户。
* 如果用户已经存在,则直接进行返回
*
* @param mobile 手机号
* @param registerIp 注册 IP
* @return 用户对象
*/
MemberUserDO createUserIfAbsent(@Mobile String mobile, String registerIp,String from);
/**
* 更新用户的最后登陆信息
*
* @param id 用户编号
* @param loginIp 登陆 IP
*/
void updateUserLogin(Long id, String loginIp);
/**
* 通过用户 ID 查询用户
*
* @param id 用户ID
* @return 用户对象信息
*/
MemberUserDO getUser(Long id);
/**
* 通过用户 ID 查询用户
*
* @param id 用户ID
* @return 用户对象信息
*/
AppUserQueryVo getAppUser(Long id);
/**
* 减去用户余额
* @param uid uid
* @param payPrice 金额
*/
void decPrice(Long uid, BigDecimal payPrice);
/**
* 减去用户积分
* @param uid uid
* @param score 积分
*/
void decScore(Long uid, Integer score);
/**
* 增加购买次数
* @param uid uid
*/
void incPayCount(Long uid);
/**
* 通过用户 ID 查询用户们
*
* @param ids 用户 ID
* @return 用户对象信息数组
*/
List<MemberUserDO> getUserList(Collection<Long> ids);
/**
* 修改用户昵称
* @param userId 用户id
* @param nickname 用户新昵称
*/
void updateUserNickname(Long userId, String nickname,String birthday, Integer gender,
String avatar,
String mobile);
/**
* 修改用户头像
* @param userId 用户id
* @param inputStream 头像文件
* @return 头像url
*/
String updateUserAvatar(Long userId, InputStream inputStream) throws Exception;
/**
* 修改手机
* @param userId 用户id
* @param reqVO 请求实体
*/
void updateUserMobile(Long userId, AppUserUpdateMobileReqVO reqVO);
/**
* 判断密码是否匹配
*
* @param rawPassword 未加密的密码
* @param encodedPassword 加密后的密码
* @return 是否匹配
*/
boolean isPasswordMatch(String rawPassword, String encodedPassword);
/**
* 更新用户余额
* @param uid y用户id
* @param price 金额
*/
void incMoney(Long uid,BigDecimal price);
}
@@ -0,0 +1,274 @@
package co.yixiang.yshop.module.member.service.user;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import co.yixiang.yshop.framework.common.enums.CommonStatusEnum;
import co.yixiang.yshop.framework.common.enums.ShopCommonEnum;
import co.yixiang.yshop.module.coupon.dal.dataobject.couponuser.CouponUserDO;
import co.yixiang.yshop.module.coupon.service.couponuser.AppCouponUserService;
import co.yixiang.yshop.module.infra.api.file.FileApi;
import co.yixiang.yshop.module.member.controller.app.user.vo.AppUserQueryVo;
import co.yixiang.yshop.module.member.controller.app.user.vo.AppUserUpdateMobileReqVO;
import co.yixiang.yshop.module.member.convert.user.UserConvert;
import co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;
import co.yixiang.yshop.module.member.dal.dataobject.userbill.UserBillDO;
import co.yixiang.yshop.module.member.dal.mysql.user.MemberUserMapper;
import co.yixiang.yshop.module.member.dal.mysql.userbill.UserBillMapper;
import co.yixiang.yshop.module.member.enums.BillDetailEnum;
import co.yixiang.yshop.module.member.service.userbill.UserBillService;
import co.yixiang.yshop.module.system.api.sms.SmsCodeApi;
import co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.io.InputStream;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;
import static co.yixiang.yshop.framework.common.util.servlet.ServletUtils.getClientIP;
import static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.USER_NOT_EXISTS;
/**
* 会员 User Service 实现类
*
* @author yshop
*/
@Service
@Valid
@Slf4j
public class MemberUserServiceImpl extends ServiceImpl<MemberUserMapper,MemberUserDO> implements MemberUserService {
@Resource
private MemberUserMapper memberUserMapper;
@Resource
private FileApi fileApi;
@Resource
private SmsCodeApi smsCodeApi;
@Resource
private PasswordEncoder passwordEncoder;
@Resource
private AppCouponUserService appCouponUserService;
@Resource
private UserBillMapper userBillMapper;
@Resource
private UserBillService billService;
@Override
public MemberUserDO getUserByMobile(String mobile) {
return memberUserMapper.selectByMobile(mobile);
}
@Override
public List<MemberUserDO> getUserListByNickname(String nickname) {
return memberUserMapper.selectListByNicknameLike(nickname);
}
@Override
public MemberUserDO createUserIfAbsent(String mobile, String registerIp,String from) {
// 用户已经存在
if(StrUtil.isNotEmpty(mobile)){
MemberUserDO user = memberUserMapper.selectByMobile(mobile);
if (user != null) {
return user;
}
}
// 用户不存在,则进行创建
return this.createUser(mobile, registerIp,from);
}
private MemberUserDO createUser(String mobile, String registerIp,String from) {
// 生成密码
String password = IdUtil.fastSimpleUUID();
// 插入用户
MemberUserDO user = new MemberUserDO();
user.setNickname(mobile);
user.setUsername(mobile);
user.setMobile(mobile);
user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
user.setPassword(encodePassword(password)); // 加密密码
user.setRegisterIp(registerIp);
user.setLoginType(from);
memberUserMapper.insert(user);
return user;
}
@Override
public void updateUserLogin(Long id, String loginIp) {
memberUserMapper.updateById(new MemberUserDO().setId(id)
.setLoginIp(loginIp).setLoginDate(LocalDateTime.now()));
}
@Override
public MemberUserDO getUser(Long id) {
return memberUserMapper.selectById(id);
}
@Override
public AppUserQueryVo getAppUser(Long id){
AppUserQueryVo appUserQueryVo = UserConvert.INSTANCE.convert3(memberUserMapper.selectById(id));
Long count = appCouponUserService.count(new LambdaQueryWrapper<CouponUserDO>()
.eq(CouponUserDO::getUserId,id)
.eq(CouponUserDO::getStatus, ShopCommonEnum.IS_STATUS_1));
appUserQueryVo.setCouponCount(count);
QueryWrapper<UserBillDO> wrapper = new QueryWrapper<>();
wrapper.select("SUM(number) as sumAll")
.eq("category", BillDetailEnum.CATEGORY_1.getValue())
.eq("type",BillDetailEnum.TYPE_3.getValue()).eq("uid",id);
UserBillDO userBillDO = userBillMapper.selectOne(wrapper);
if(userBillDO == null){
appUserQueryVo.setSumMoney(BigDecimal.ZERO);
}else{
appUserQueryVo.setSumMoney(userBillDO.getSumAll());
}
return appUserQueryVo;
}
/**
* 减去用户余额
* @param uid uid
* @param payPrice 金额
*/
@Override
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void decPrice(Long uid, BigDecimal payPrice) {
memberUserMapper.decPrice(payPrice,uid);
}
/**
* 减去用户积分
* @param uid uid
* @param score 积分
*/
@Override
public void decScore(Long uid, Integer score) {
memberUserMapper.decScore(score,uid);
}
/**
* 增加购买次数
* @param uid uid
*/
@Override
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void incPayCount(Long uid) {
memberUserMapper.incPayCount(uid);
}
@Override
public List<MemberUserDO> getUserList(Collection<Long> ids) {
return memberUserMapper.selectBatchIds(ids);
}
@Override
public void updateUserNickname(Long userId, String nickname,String birthday, Integer gender,
String avatar,
String mobile) {
MemberUserDO user = this.checkUserExists(userId);
// 仅当新昵称不等于旧昵称时进行修改
// if (nickname.equals(user.getNickname())){
// return;
// }
MemberUserDO userDO = new MemberUserDO();
userDO.setId(user.getId());
userDO.setNickname(nickname);
userDO.setBirthday(birthday);
userDO.setGender(gender);
userDO.setAvatar(avatar);
userDO.setMobile(mobile);
memberUserMapper.updateById(userDO);
}
@Override
public String updateUserAvatar(Long userId, InputStream avatarFile) throws Exception {
this.checkUserExists(userId);
// 创建文件
String avatar = fileApi.createFile(IoUtil.readBytes(avatarFile));
// 更新头像路径
memberUserMapper.updateById(MemberUserDO.builder().id(userId).avatar(avatar).build());
return avatar;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateUserMobile(Long userId, AppUserUpdateMobileReqVO reqVO) {
// 检测用户是否存在
checkUserExists(userId);
// TODO yshop:oldMobile 应该不用传递
// 校验旧手机和旧验证码
smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(reqVO.getOldMobile()).setCode(reqVO.getOldCode())
.setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()).setUsedIp(getClientIP()));
// 使用新验证码
smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(reqVO.getMobile()).setCode(reqVO.getCode())
.setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()).setUsedIp(getClientIP()));
// 更新用户手机
memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(reqVO.getMobile()).build());
}
@Override
public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
return passwordEncoder.matches(rawPassword, encodedPassword);
}
/**
* 更新用户余额
* @param uid y用户id
* @param price 金额
*/
@Override
public void incMoney(Long uid, BigDecimal price) {
if(price!=null&&price.doubleValue()>0){
memberUserMapper.incMoney(uid,price);
}
}
/**
* 对密码进行加密
*
* @param password 密码
* @return 加密后的密码
*/
private String encodePassword(String password) {
return passwordEncoder.encode(password);
}
@VisibleForTesting
public MemberUserDO checkUserExists(Long id) {
if (id == null) {
return null;
}
MemberUserDO user = memberUserMapper.selectById(id);
if (user == null) {
throw exception(USER_NOT_EXISTS);
}
return user;
}
}
@@ -0,0 +1,77 @@
package co.yixiang.yshop.module.member.service.user;
import java.util.*;
import co.yixiang.yshop.module.member.controller.admin.user.vo.*;
import co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import jakarta.validation.Valid;
/**
* 用户 Service 接口
*
* @author yshop
*/
public interface UserService {
/**
* 创建用户
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createUser(@Valid UserCreateReqVO createReqVO);
/**
* 更新用户
*
* @param updateReqVO 更新信息
*/
void updateUser(@Valid UserUpdateReqVO updateReqVO);
/**
* 更新金额与积分
*
* @param updateReqVO 更新信息
*/
void updateMony(@Valid UserUpdateMoneyReqVO updateReqVO);
/**
* 删除用户
*
* @param id 编号
*/
void deleteUser(Long id);
/**
* 获得用户
*
* @param id 编号
* @return 用户
*/
MemberUserDO getUser(Long id);
/**
* 获得用户列表
*
* @param ids 编号
* @return 用户列表
*/
List<MemberUserDO> getUserList(Collection<Long> ids);
/**
* 获得用户分页
*
* @param pageReqVO 分页查询
* @return 用户分页
*/
PageResult<MemberUserDO> getUserPage(UserPageReqVO pageReqVO);
/**
* 获得用户列表, 用于 Excel 导出
*
* @param exportReqVO 查询条件
* @return 用户列表
*/
List<MemberUserDO> getUserList(UserExportReqVO exportReqVO);
}
@@ -0,0 +1,140 @@
package co.yixiang.yshop.module.member.service.user;
import cn.hutool.core.util.NumberUtil;
import co.yixiang.yshop.framework.common.enums.ShopCommonEnum;
import co.yixiang.yshop.module.member.enums.BillDetailEnum;
import co.yixiang.yshop.module.member.service.userbill.UserBillService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.*;
import co.yixiang.yshop.module.member.controller.admin.user.vo.*;
import co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import co.yixiang.yshop.module.member.convert.user.UserConvert;
import co.yixiang.yshop.module.member.dal.mysql.user.MemberUserMapper;
import static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;
import static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.*;
/**
* 用户 Service 实现类
*
* @author yshop
*/
@Service
@Validated
public class UserServiceImpl implements UserService {
@Resource
private MemberUserMapper userMapper;
@Resource
private UserBillService userBillService;
@Override
public Long createUser(UserCreateReqVO createReqVO) {
// 插入
MemberUserDO user = UserConvert.INSTANCE.convert(createReqVO);
userMapper.insert(user);
// 返回
return user.getId();
}
@Override
public void updateUser(UserUpdateReqVO updateReqVO) {
// 校验存在
validateUserExists(updateReqVO.getId());
// 更新
MemberUserDO updateObj = UserConvert.INSTANCE.convert(updateReqVO);
userMapper.updateById(updateObj);
}
/**
* 更新金额与积分
*
* @param updateReqVO 更新信息
*/
@Override
public void updateMony(UserUpdateMoneyReqVO updateReqVO){
MemberUserDO memberUserDO = userMapper.selectById(updateReqVO.getId());
double newMoney = 0d;
String mark = "";
if(ShopCommonEnum.ADD_1.getValue().equals(updateReqVO.getPtype())){
mark = "系统增加了"+updateReqVO.getMoney()+"余额";
newMoney = NumberUtil.add(memberUserDO.getNowMoney(),new BigDecimal(updateReqVO.getMoney())).doubleValue();
userBillService.income(memberUserDO.getId(),"系统增加余额", BillDetailEnum.CATEGORY_1.getValue(),
BillDetailEnum.TYPE_6.getValue(),Double.valueOf(updateReqVO.getMoney()),newMoney, mark,"");
}else{
mark = "系统扣除了"+updateReqVO.getMoney()+"余额";
newMoney = NumberUtil.sub(memberUserDO.getNowMoney(),new BigDecimal(updateReqVO.getMoney())).doubleValue();
if(newMoney < 0) {
newMoney = 0d;
}
userBillService.expend(memberUserDO.getId(), "系统减少余额",
BillDetailEnum.CATEGORY_1.getValue(),
BillDetailEnum.TYPE_7.getValue(),
Double.valueOf(updateReqVO.getMoney()), newMoney, mark);
}
double newIntegral = 0d;
if(ShopCommonEnum.ADD_1.getValue().equals(updateReqVO.getItype())){
mark = "系统增加了"+updateReqVO.getIntegral()+"积分";
newIntegral = NumberUtil.add(memberUserDO.getIntegral(),new BigDecimal(updateReqVO.getIntegral())).doubleValue();
userBillService.income(memberUserDO.getId(),"系统增加积分", BillDetailEnum.CATEGORY_2.getValue(),
BillDetailEnum.TYPE_6.getValue(),Double.valueOf(updateReqVO.getIntegral()),newIntegral, mark,"");
}else{
mark = "系统扣除了"+updateReqVO.getIntegral()+"积分";
newIntegral = NumberUtil.sub(memberUserDO.getIntegral(),new BigDecimal(updateReqVO.getIntegral())).doubleValue();
if(newIntegral < 0) {
newIntegral = 0d;
}
userBillService.expend(memberUserDO.getId(), "系统减少积分",
BillDetailEnum.CATEGORY_2.getValue(),
BillDetailEnum.TYPE_7.getValue(),
Double.valueOf(updateReqVO.getIntegral()), newIntegral, mark);
}
memberUserDO.setIntegral(BigDecimal.valueOf(newIntegral));
memberUserDO.setNowMoney(BigDecimal.valueOf(newMoney));
userMapper.updateById(memberUserDO);
}
@Override
public void deleteUser(Long id) {
// 校验存在
validateUserExists(id);
// 删除
userMapper.deleteById(id);
}
private void validateUserExists(Long id) {
if (userMapper.selectById(id) == null) {
throw exception(USER_NOT_EXISTS);
}
}
@Override
public MemberUserDO getUser(Long id) {
return userMapper.selectById(id);
}
@Override
public List<MemberUserDO> getUserList(Collection<Long> ids) {
return userMapper.selectBatchIds(ids);
}
@Override
public PageResult<MemberUserDO> getUserPage(UserPageReqVO pageReqVO) {
return userMapper.selectPage(pageReqVO);
}
@Override
public List<MemberUserDO> getUserList(UserExportReqVO exportReqVO) {
return userMapper.selectList(exportReqVO);
}
}
@@ -0,0 +1,41 @@
//package co.yixiang.yshop.module.member.service.user.dto;
//
//import lombok.*;
//
///**
// * @ClassName WechatUserDTO
// * @Author hupeng <610796224@qq.com>
// * @Date 2023/7/18
// **/
//@Getter
//@Setter
//@Builder
//@AllArgsConstructor
//@NoArgsConstructor
//public class WechatUserDto {
//
// private String openid;
//
// private String unionId;
//
// private String routineOpenid;
//
// private String nickname;
//
// private String headimgurl;
//
// private Integer sex;
//
// private String city;
//
// private String language;
//
// private String province;
//
// private String country;
//
// private Boolean subscribe;
//
// private Long subscribeTime;
//
//}
@@ -0,0 +1,41 @@
package co.yixiang.yshop.module.member.service.useraddress;
import co.yixiang.yshop.module.member.controller.app.address.param.AppAddressParam;
import co.yixiang.yshop.module.member.controller.app.address.vo.AppUserAddressQueryVo;
import co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* 用户地址 Service 接口
*
* @author yshop
*/
public interface AppUserAddressService extends IService<UserAddressDO> {
/**
* 添加或者修改地址
* @param uid uid
* @param param AddressParam
*/
Long addAndEdit(Long uid, AppAddressParam param);
/**
* 设置默认地址
* @param uid uid
* @param addressId 地址id
*/
void setDefault(Long uid,String addressId);
/**
* 获取用户地址
* @param uid uid
* @param page page
* @param limit limit
* @return List
*/
List<AppUserAddressQueryVo> getList(Long uid, int page, int limit);
}
@@ -0,0 +1,104 @@
package co.yixiang.yshop.module.member.service.useraddress;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import co.yixiang.yshop.framework.common.enums.ShopCommonEnum;
import co.yixiang.yshop.module.member.controller.app.address.param.AppAddressParam;
import co.yixiang.yshop.module.member.controller.app.address.vo.AppUserAddressQueryVo;
import co.yixiang.yshop.module.member.convert.useraddress.UserAddressConvert;
import co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;
import co.yixiang.yshop.module.member.dal.mysql.useraddress.UserAddressMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;
import static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.USER_ADDRESS_NOT_EXISTS;
import static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.USER_ADDRESS_PARAM_NOT_EXISTS;
/**
* 用户地址 Service 实现类
*
* @author yshop
*/
@Service
@Validated
public class AppUserAddressServiceImpl extends ServiceImpl<UserAddressMapper,UserAddressDO> implements AppUserAddressService {
@Resource
private UserAddressMapper userAddressMapper;
/**
* 添加或者修改地址
* @param uid uid
* @param param AddressParam
* @return id 地址id
*/
@Override
public Long addAndEdit(Long uid, AppAddressParam param){
UserAddressDO userAddress = UserAddressDO.builder()
.address(param.getAddress())
.detail(param.getDetail())
.uid(uid)
.isDefault(param.getIsDefault())
.phone(param.getPhone())
.postCode(param.getPostCode())
.realName(param.getRealName())
.latitude(param.getLatitude())
.longitude(param.getLongitude())
.build();
if(StrUtil.isBlank(param.getId())){
this.save(userAddress);
}else{
userAddress.setId(Long.valueOf(param.getId()));
this.updateById(userAddress);
}
return userAddress.getId();
}
/**
* 设置默认地址
* @param uid uid
* @param addressId 地址id
*/
@Override
public void setDefault(Long uid,String addressId){
if(StrUtil.isBlank(addressId) || !NumberUtil.isNumber(addressId)){
throw exception(USER_ADDRESS_PARAM_NOT_EXISTS);
}
UserAddressDO address = new UserAddressDO();
address.setIsDefault(ShopCommonEnum.DEFAULT_0.getValue());
userAddressMapper.update(address,
new LambdaQueryWrapper<UserAddressDO>().eq(UserAddressDO::getUid,uid));
UserAddressDO userAddress = new UserAddressDO();
userAddress.setIsDefault(ShopCommonEnum.DEFAULT_1.getValue());
userAddress.setId(Long.valueOf(addressId));
userAddressMapper.updateById(userAddress);
}
/**
* 获取用户地址
* @param uid uid
* @param page page
* @param limit limit
* @return List
*/
@Override
public List<AppUserAddressQueryVo> getList(Long uid, int page, int limit){
Page<UserAddressDO> pageModel = new Page<>(page, limit);
IPage<UserAddressDO> pageList = this.lambdaQuery().eq(UserAddressDO::getUid,uid).page(pageModel);
return UserAddressConvert.INSTANCE.convertList02(pageList.getRecords());
}
}
@@ -0,0 +1,70 @@
package co.yixiang.yshop.module.member.service.useraddress;
import java.util.*;
import co.yixiang.yshop.module.member.controller.admin.useraddress.vo.*;
import co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import jakarta.validation.Valid;
/**
* 用户地址 Service 接口
*
* @author yshop
*/
public interface UserAddressService {
/**
* 创建用户地址
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createUserAddress(@Valid UserAddressCreateReqVO createReqVO);
/**
* 更新用户地址
*
* @param updateReqVO 更新信息
*/
void updateUserAddress(@Valid UserAddressUpdateReqVO updateReqVO);
/**
* 删除用户地址
*
* @param id 编号
*/
void deleteUserAddress(Long id);
/**
* 获得用户地址
*
* @param id 编号
* @return 用户地址
*/
UserAddressDO getUserAddress(Long id);
/**
* 获得用户地址列表
*
* @param ids 编号
* @return 用户地址列表
*/
List<UserAddressDO> getUserAddressList(Collection<Long> ids);
/**
* 获得用户地址分页
*
* @param pageReqVO 分页查询
* @return 用户地址分页
*/
PageResult<UserAddressDO> getUserAddressPage(UserAddressPageReqVO pageReqVO);
/**
* 获得用户地址列表, 用于 Excel 导出
*
* @param exportReqVO 查询条件
* @return 用户地址列表
*/
List<UserAddressDO> getUserAddressList(UserAddressExportReqVO exportReqVO);
}
@@ -0,0 +1,82 @@
package co.yixiang.yshop.module.member.service.useraddress;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.*;
import co.yixiang.yshop.module.member.controller.admin.useraddress.vo.*;
import co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import co.yixiang.yshop.module.member.convert.useraddress.UserAddressConvert;
import co.yixiang.yshop.module.member.dal.mysql.useraddress.UserAddressMapper;
import static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;
import static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.*;
/**
* 用户地址 Service 实现类
*
* @author yshop
*/
@Service
@Validated
public class UserAddressServiceImpl implements UserAddressService {
@Resource
private UserAddressMapper userAddressMapper;
@Override
public Long createUserAddress(UserAddressCreateReqVO createReqVO) {
// 插入
UserAddressDO userAddress = UserAddressConvert.INSTANCE.convert(createReqVO);
userAddressMapper.insert(userAddress);
// 返回
return userAddress.getId();
}
@Override
public void updateUserAddress(UserAddressUpdateReqVO updateReqVO) {
// 校验存在
validateUserAddressExists(updateReqVO.getId());
// 更新
UserAddressDO updateObj = UserAddressConvert.INSTANCE.convert(updateReqVO);
userAddressMapper.updateById(updateObj);
}
@Override
public void deleteUserAddress(Long id) {
// 校验存在
validateUserAddressExists(id);
// 删除
userAddressMapper.deleteById(id);
}
private void validateUserAddressExists(Long id) {
if (userAddressMapper.selectById(id) == null) {
throw exception(USER_ADDRESS_NOT_EXISTS);
}
}
@Override
public UserAddressDO getUserAddress(Long id) {
return userAddressMapper.selectById(id);
}
@Override
public List<UserAddressDO> getUserAddressList(Collection<Long> ids) {
return userAddressMapper.selectBatchIds(ids);
}
@Override
public PageResult<UserAddressDO> getUserAddressPage(UserAddressPageReqVO pageReqVO) {
return userAddressMapper.selectPage(pageReqVO);
}
@Override
public List<UserAddressDO> getUserAddressList(UserAddressExportReqVO exportReqVO) {
return userAddressMapper.selectList(exportReqVO);
}
}
@@ -0,0 +1,68 @@
package co.yixiang.yshop.module.member.service.userbill;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import co.yixiang.yshop.module.member.controller.admin.userbill.vo.UserBillPageReqVO;
import co.yixiang.yshop.module.member.controller.app.user.vo.AppUserBillVO;
import co.yixiang.yshop.module.member.dal.dataobject.userbill.UserBillDO;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* 用户账单 Service 接口
*
* @author yshop
*/
public interface UserBillService extends IService<UserBillDO> {
/**
* 获得用户账单分页
*
* @param pageReqVO 分页查询
* @return 用户账单分页
*/
PageResult<UserBillDO> getUserBillPage(UserBillPageReqVO pageReqVO);
/**
* 增加支出流水
* @param uid uid
* @param title 账单标题
* @param category 明细种类
* @param type 明细类型
* @param number 明细数字
* @param balance 剩余
* @param mark 备注
*/
void expend(Long uid,String title,String category,String type,double number,double balance,String mark);
/**
* 增加收入/支入流水
* @param uid uid
* @param title 账单标题
* @param category 明细种类
* @param type 明细类型
* @param number 明细数字
* @param balance 剩余
* @param mark 备注
* @param linkid 关联id
*/
void income(Long uid,String title,String category,String type,double number,
double balance,String mark,String linkid);
void income(Long uid,String title,String category,String type,double number,
double balance,String mark,String linkid,String extendField);
/**
* 获取用户账单
* @param uid
* @param cate 0余额 1-积分
* @param type 状态,0全部 1消费 2充值 3退款
* @param page
* @param limit
* @return
*/
List<AppUserBillVO> getBillList(Long uid, int cate, int type,int page, int limit);
}
@@ -0,0 +1,158 @@
package co.yixiang.yshop.module.member.service.userbill;
import co.yixiang.yshop.framework.common.enums.ShopCommonEnum;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import co.yixiang.yshop.module.member.controller.admin.userbill.vo.UserBillPageReqVO;
import co.yixiang.yshop.module.member.controller.app.user.vo.AppUserBillVO;
import co.yixiang.yshop.module.member.convert.userbill.UserBillConvert;
import co.yixiang.yshop.module.member.dal.dataobject.userbill.UserBillDO;
import co.yixiang.yshop.module.member.dal.mysql.userbill.UserBillMapper;
import co.yixiang.yshop.module.member.enums.BillDetailEnum;
import co.yixiang.yshop.module.member.enums.BillEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.List;
/**
* 用户账单 Service 实现类
*
* @author yshop
*/
@Service
@Validated
public class UserBillServiceImpl extends ServiceImpl<UserBillMapper, UserBillDO> implements UserBillService {
@Resource
private UserBillMapper userBillMapper;
@Override
public PageResult<UserBillDO> getUserBillPage(UserBillPageReqVO pageReqVO) {
return userBillMapper.selectPage(pageReqVO);
}
/**
* 增加支出流水
* @param uid uid
* @param title 账单标题
* @param category 明细种类
* @param type 明细类型
* @param number 明细数字
* @param balance 剩余
* @param mark 备注
*/
@Override
public void expend(Long uid,String title,String category,String type,double number,double balance,String mark){
UserBillDO userBill = UserBillDO.builder()
.uid(uid)
.title(title)
.category(category)
.type(type)
.number(BigDecimal.valueOf(number))
.balance(BigDecimal.valueOf(balance))
.mark(mark)
.pm(BillEnum.PM_0.getValue())
.build();
userBillMapper.insert(userBill);
}
/**
* 增加收入/支入流水
* @param uid uid
* @param title 账单标题
* @param category 明细种类
* @param type 明细类型
* @param number 明细数字
* @param balance 剩余
* @param mark 备注
* @param linkid 关联id
*/
@Override
public void income(Long uid,String title,String category,String type,double number,
double balance,String mark,String linkid){
UserBillDO userBill = UserBillDO.builder()
.uid(uid)
.title(title)
.category(category)
.type(type)
.number(BigDecimal.valueOf(number))
.balance(BigDecimal.valueOf(balance))
.mark(mark)
.pm(BillEnum.PM_1.getValue())
.linkId(linkid)
.build();
userBillMapper.insert(userBill);
}
@Override
public void income(Long uid, String title, String category, String type, double number, double balance,
String mark, String linkid, String extendField) {
UserBillDO userBill = UserBillDO.builder()
.uid(uid)
.title(title)
.category(category)
.type(type)
.number(BigDecimal.valueOf(number))
.balance(BigDecimal.valueOf(balance))
.mark(mark)
.pm(BillEnum.PM_1.getValue())
.linkId(linkid)
.extendField(extendField)
.status(ShopCommonEnum.IS_STATUS_0.getValue())
.build();
userBillMapper.insert(userBill);
}
/**
* 获取用户账单
* @param uid
* @param cate 0余额 1-积分
* @param type 状态,0全部 1消费 2充值 3退款
* @param page
* @param limit
* @return
*/
@Override
public List<AppUserBillVO> getBillList(Long uid,int cate, int type, int page, int limit) {
LambdaQueryWrapper<UserBillDO> wrapper = new LambdaQueryWrapper<>();
if(cate == 1){
wrapper.eq(UserBillDO::getCategory,BillDetailEnum.CATEGORY_2.getValue());
}else{
wrapper.eq(UserBillDO::getCategory,BillDetailEnum.CATEGORY_1.getValue());
}
wrapper.eq(UserBillDO::getUid, uid)
.eq(UserBillDO::getStatus,ShopCommonEnum.IS_STATUS_1.getValue())
//.eq(UserBillDO::getCategory,BillDetailEnum.CATEGORY_1.getValue())
.orderByDesc(UserBillDO::getId);
switch (type){
case 1:
wrapper.eq(UserBillDO::getType, BillDetailEnum.TYPE_3.getValue());
break;
case 2:
wrapper.eq(UserBillDO::getType, BillDetailEnum.TYPE_1.getValue());
break;
case 3:
wrapper.eq(UserBillDO::getType, BillDetailEnum.TYPE_5.getValue());
break;
default:
}
Page<UserBillDO> pageModel = new Page<>(page, limit);
IPage<UserBillDO> pageList = userBillMapper.selectPage(pageModel, wrapper);
return UserBillConvert.INSTANCE.convertList02(pageList.getRecords());
}
}
@@ -0,0 +1,126 @@
package co.yixiang.yshop.module.member.service.auth;
import cn.binarywang.wx.miniapp.api.WxMaService;
import co.yixiang.yshop.framework.common.enums.CommonStatusEnum;
import co.yixiang.yshop.framework.common.util.collection.ArrayUtils;
import co.yixiang.yshop.framework.redis.config.YshopRedisAutoConfiguration;
import co.yixiang.yshop.framework.test.core.ut.BaseDbAndRedisUnitTest;
import co.yixiang.yshop.module.member.controller.app.auth.vo.AppAuthResetPasswordReqVO;
import co.yixiang.yshop.module.member.controller.app.auth.vo.AppAuthUpdatePasswordReqVO;
import co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;
import co.yixiang.yshop.module.member.dal.mysql.user.MemberUserMapper;
import co.yixiang.yshop.module.member.service.user.MemberUserService;
import co.yixiang.yshop.module.system.api.oauth2.OAuth2TokenApi;
import co.yixiang.yshop.module.system.api.logger.LoginLogApi;
import co.yixiang.yshop.module.system.api.sms.SmsCodeApi;
import co.yixiang.yshop.module.system.api.social.SocialUserApi;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource;
import java.util.function.Consumer;
import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.hutool.core.util.RandomUtil.randomNumbers;
import static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomPojo;
import static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
// TODO @yshop:单测的 review,等逻辑都达成一致后
/**
* {@link MemberAuthService} 的单元测试类
*
* @author 宋天
*/
@Import({MemberAuthServiceImpl.class, YshopRedisAutoConfiguration.class})
public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest {
// TODO @yshop:登录相关的单测,待补全
@Resource
private MemberAuthServiceImpl authService;
@MockBean
private MemberUserService userService;
@MockBean
private SmsCodeApi smsCodeApi;
@MockBean
private LoginLogApi loginLogApi;
@MockBean
private OAuth2TokenApi oauth2TokenApi;
@MockBean
private SocialUserApi socialUserApi;
@MockBean
private WxMaService wxMaService;
@MockBean
private PasswordEncoder passwordEncoder;
@Resource
private MemberUserMapper memberUserMapper;
@Test
public void testUpdatePassword_success(){
// 准备参数
MemberUserDO userDO = randomUserDO();
memberUserMapper.insert(userDO);
// 新密码
String newPassword = randomString();
// 请求实体
AppAuthUpdatePasswordReqVO reqVO = AppAuthUpdatePasswordReqVO.builder()
.oldPassword(userDO.getPassword())
.password(newPassword)
.build();
// 测试桩
// 这两个相等是为了返回ture这个结果
when(passwordEncoder.matches(reqVO.getOldPassword(),reqVO.getOldPassword())).thenReturn(true);
when(passwordEncoder.encode(newPassword)).thenReturn(newPassword);
// 更新用户密码
authService.updatePassword(userDO.getId(), reqVO);
assertEquals(memberUserMapper.selectById(userDO.getId()).getPassword(),newPassword);
}
@Test
public void testResetPassword_success(){
// 准备参数
MemberUserDO userDO = randomUserDO();
memberUserMapper.insert(userDO);
// 随机密码
String password = randomNumbers(11);
// 随机验证码
String code = randomNumbers(4);
// mock
when(passwordEncoder.encode(password)).thenReturn(password);
// 更新用户密码
AppAuthResetPasswordReqVO reqVO = new AppAuthResetPasswordReqVO();
reqVO.setMobile(userDO.getMobile());
reqVO.setPassword(password);
reqVO.setCode(code);
authService.resetPassword(reqVO);
assertEquals(memberUserMapper.selectById(userDO.getId()).getPassword(),password);
}
// ========== 随机对象 ==========
@SafeVarargs
private static MemberUserDO randomUserDO(Consumer<MemberUserDO>... consumers) {
Consumer<MemberUserDO> consumer = (o) -> {
o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
o.setPassword(randomString());
};
return randomPojo(MemberUserDO.class, ArrayUtils.append(consumer, consumers));
}
}
@@ -0,0 +1,49 @@
spring:
main:
lazy-initialization: true # 开启懒加载,加快速度
banner-mode: off # 单元测试,禁用 Banner
--- #################### 数据库相关配置 ####################
spring:
# 数据源配置项
datasource:
name: yshop-pro
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
driver-class-name: org.h2.Driver
username: sa
password:
druid:
async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
initial-size: 1 # 单元测试,配置为 1,提升启动速度
sql:
init:
schema-locations: classpath:/sql/create_tables.sql
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis:
host: 127.0.0.1 # 地址
port: 16379 # 端口(单元测试,使用 16379 端口)
database: 0 # 数据库索引
mybatis:
lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
--- #################### 定时任务相关配置 ####################
--- #################### 配置中心相关配置 ####################
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项(单元测试,禁用 Lock4j)
# Resilience4j 配置项
--- #################### 监控相关配置 ####################
--- #################### yshop相关配置 ####################
# yshop配置项,设置当前项目所有自定义的配置
yshop:
info:
base-package: co.yixiang.yshop.module
@@ -0,0 +1,4 @@
<configuration>
<!-- 引用 Spring Boot 的 logback 基础配置 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
</configuration>
@@ -0,0 +1,2 @@
DELETE FROM "member_user";
DELETE FROM "member_address";
Loaded 100 of 3676 files, more files were not shown because too many files have changed in this diff. Show more