mirror of
https://gitee.com/hhyykk/ipms-sjy.git
synced 2025-09-10 15:11:54 +08:00
Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/1.8.0-uniapp
Conflicts: sql/ruoyi-vue-pro.sql yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/AppAddressController.http yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/AppAddressController.java yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/package-info.java yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/vo/AppAddressBaseVO.java yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/vo/AppAddressCreateReqVO.java yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/vo/AppAddressRespVO.java yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/vo/AppAddressUpdateReqVO.java yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/address/AddressConvert.java yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/address/AddressDO.java yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/address/package-info.java yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/address/AddressMapper.java yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/enums/AddressTypeEnum.java yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/address/AddressService.java yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/address/AddressServiceImpl.java yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/address/AddressServiceImplTest.java
This commit is contained in:
84
yudao-module-member/yudao-module-member-biz/pom.xml
Normal file
84
yudao-module-member/yudao-module-member-biz/pom.xml
Normal file
@@ -0,0 +1,84 @@
|
||||
<?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>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-member</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>yudao-module-member-biz</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>
|
||||
member 模块,我们放会员业务。
|
||||
例如说:会员中心等等
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-member-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-system-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-infra-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 业务组件 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-weixin</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DB 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 消息队列相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-mq</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- 工具类相关 -->
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@@ -0,0 +1 @@
|
||||
package cn.iocoder.yudao.module.member.api;
|
@@ -0,0 +1,30 @@
|
||||
package cn.iocoder.yudao.module.member.api.user;
|
||||
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
|
||||
import cn.iocoder.yudao.module.member.convert.user.UserConvert;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 会员用户的 API 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class MemberUserApiImpl implements MemberUserApi {
|
||||
|
||||
@Resource
|
||||
private MemberUserService userService;
|
||||
|
||||
@Override
|
||||
public UserRespDTO getUser(Long id) {
|
||||
MemberUserDO user = userService.getUser(id);
|
||||
return UserConvert.INSTANCE.convert2(user);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1 @@
|
||||
package cn.iocoder.yudao.module.member.controller.admin.address;
|
@@ -0,0 +1,4 @@
|
||||
package cn.iocoder.yudao.module.member.controller.admin.user;
|
||||
|
||||
public class UserController {
|
||||
}
|
@@ -0,0 +1 @@
|
||||
package cn.iocoder.yudao.module.member.controller.admin.user;
|
@@ -0,0 +1,54 @@
|
||||
### 请求 /create 接口 => 成功
|
||||
POST {{appApi}}//member/address/create
|
||||
Content-Type: application/json
|
||||
tenant-id: {{appTenentId}}
|
||||
Authorization: Bearer 2510e2e4287346eb8e36353a55e27fd6
|
||||
|
||||
{
|
||||
"userId": "245",
|
||||
"name": "yunai",
|
||||
"mobile": "15601691300",
|
||||
"areaCode": "610632",
|
||||
"detailAddress": "芋道源码 233 号 666 室",
|
||||
"type": "1"
|
||||
}
|
||||
|
||||
### 请求 /update 接口 => 成功
|
||||
PUT {{appApi}}//member/address/update
|
||||
Content-Type: application/json
|
||||
tenant-id: {{appTenentId}}
|
||||
Authorization: Bearer 2510e2e4287346eb8e36353a55e27fd6
|
||||
|
||||
{
|
||||
"id": "1",
|
||||
"userId": "245",
|
||||
"name": "yunai888",
|
||||
"mobile": "15601691300",
|
||||
"areaCode": "610632",
|
||||
"detailAddress": "芋道源码 233 号 666 室",
|
||||
"type": "1"
|
||||
}
|
||||
|
||||
### 请求 /delete 接口 => 成功
|
||||
DELETE {{appApi}}//member/address/delete?id=2
|
||||
Content-Type: application/json
|
||||
tenant-id: {{appTenentId}}
|
||||
Authorization: Bearer fa4848b001de4eae9faf516c0c8520f8
|
||||
|
||||
### 请求 /get 接口 => 成功
|
||||
GET {{appApi}}//member/address/get?id=1
|
||||
Content-Type: application/json
|
||||
tenant-id: {{appTenentId}}
|
||||
Authorization: Bearer fa4848b001de4eae9faf516c0c8520f8
|
||||
|
||||
### 请求 /get-default 接口 => 成功
|
||||
GET {{appApi}}//member/address/get-default
|
||||
Content-Type: application/json
|
||||
tenant-id: {{appTenentId}}
|
||||
Authorization: Bearer fa4848b001de4eae9faf516c0c8520f8
|
||||
|
||||
### 请求 /list 接口 => 成功
|
||||
GET {{appApi}}//member/address/list
|
||||
Content-Type: application/json
|
||||
tenant-id: {{appTenentId}}
|
||||
Authorization: Bearer fa4848b001de4eae9faf516c0c8520f8
|
@@ -0,0 +1,75 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.address;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressCreateReqVO;
|
||||
import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressRespVO;
|
||||
import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.member.convert.address.AddressConvert;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.address.AddressDO;
|
||||
import cn.iocoder.yudao.module.member.service.address.AddressService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Api(tags = "用户 APP - 用户收件地址")
|
||||
@RestController
|
||||
@RequestMapping("/member/address")
|
||||
@Validated
|
||||
public class AppAddressController {
|
||||
|
||||
@Resource
|
||||
private AddressService addressService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@ApiOperation("创建用户收件地址")
|
||||
public CommonResult<Long> createAddress(@Valid @RequestBody AppAddressCreateReqVO createReqVO) {
|
||||
return success(addressService.createAddress(getLoginUserId(), createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@ApiOperation("更新用户收件地址")
|
||||
public CommonResult<Boolean> updateAddress(@Valid @RequestBody AppAddressUpdateReqVO updateReqVO) {
|
||||
addressService.updateAddress(getLoginUserId(), updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@ApiOperation("删除用户收件地址")
|
||||
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
|
||||
public CommonResult<Boolean> deleteAddress(@RequestParam("id") Long id) {
|
||||
addressService.deleteAddress(getLoginUserId(), id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@ApiOperation("获得用户收件地址")
|
||||
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
|
||||
public CommonResult<AppAddressRespVO> getAddress(@RequestParam("id") Long id) {
|
||||
AddressDO address = addressService.getAddress(getLoginUserId(), id);
|
||||
return success(AddressConvert.INSTANCE.convert(address));
|
||||
}
|
||||
|
||||
@GetMapping("/get-default")
|
||||
@ApiOperation("获得默认的用户收件地址")
|
||||
public CommonResult<AppAddressRespVO> getDefaultUserAddress() {
|
||||
AddressDO address = addressService.getDefaultUserAddress(getLoginUserId());
|
||||
return success(AddressConvert.INSTANCE.convert(address));
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@ApiOperation("获得用户收件地址列表")
|
||||
public CommonResult<List<AppAddressRespVO>> getAddressList() {
|
||||
List<AddressDO> list = addressService.getAddressList(getLoginUserId());
|
||||
return success(AddressConvert.INSTANCE.convertList(list));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.address.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.member.enums.AddressTypeEnum;
|
||||
import lombok.*;
|
||||
import io.swagger.annotations.*;
|
||||
import javax.validation.constraints.*;
|
||||
|
||||
/**
|
||||
* 用户收件地址 Base VO,提供给添加、修改、详细的子 VO 使用
|
||||
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
|
||||
*/
|
||||
@Data
|
||||
public class AppAddressBaseVO {
|
||||
|
||||
@ApiModelProperty(value = "收件人名称", required = true)
|
||||
@NotNull(message = "收件人名称不能为空")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty(value = "手机号", required = true)
|
||||
@NotNull(message = "手机号不能为空")
|
||||
private String mobile;
|
||||
|
||||
@ApiModelProperty(value = "地区编码", required = true)
|
||||
@NotNull(message = "地区编码不能为空")
|
||||
private Integer areaCode;
|
||||
|
||||
@ApiModelProperty(value = "收件详细地址", required = true)
|
||||
@NotNull(message = "收件详细地址不能为空")
|
||||
private String detailAddress;
|
||||
|
||||
@ApiModelProperty(value = "地址类型", required = true)
|
||||
@NotNull(message = "地址类型不能为空")
|
||||
@InEnum(AddressTypeEnum.class)
|
||||
private Integer type;
|
||||
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.address.vo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import io.swagger.annotations.*;
|
||||
import javax.validation.constraints.*;
|
||||
|
||||
@ApiModel("用户 APP - 用户收件地址创建 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class AppAddressCreateReqVO extends AppAddressBaseVO {
|
||||
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.address.vo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import io.swagger.annotations.*;
|
||||
|
||||
@ApiModel("用户 APP - 用户收件地址 Response VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class AppAddressRespVO extends AppAddressBaseVO {
|
||||
|
||||
@ApiModelProperty(value = "编号", required = true)
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty(value = "创建时间", required = true)
|
||||
private Date createTime;
|
||||
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.address.vo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import io.swagger.annotations.*;
|
||||
import javax.validation.constraints.*;
|
||||
|
||||
@ApiModel("用户 APP - 用户收件地址更新 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class AppAddressUpdateReqVO extends AppAddressBaseVO {
|
||||
|
||||
@ApiModelProperty(value = "编号", required = true)
|
||||
@NotNull(message = "编号不能为空")
|
||||
private Long id;
|
||||
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
### 请求 /login 接口 => 成功
|
||||
POST {{appApi}}/member/login
|
||||
Content-Type: application/json
|
||||
tenant-id: {{appTenentId}}
|
||||
|
||||
{
|
||||
"mobile": "15601691300",
|
||||
"password": "admin123"
|
||||
}
|
||||
|
||||
### 请求 /send-sms-code 接口 => 成功
|
||||
POST {{appApi}}/member/send-sms-code
|
||||
Content-Type: application/json
|
||||
tenant-id: {{appTenentId}}
|
||||
|
||||
{
|
||||
"mobile": "15601691399",
|
||||
"scene": 1
|
||||
}
|
||||
|
||||
### 请求 /sms-login 接口 => 成功
|
||||
POST {{appApi}}/member/sms-login
|
||||
Content-Type: application/json
|
||||
tenant-id: {{appTenentId}}
|
||||
|
||||
{
|
||||
"mobile": "15601691301",
|
||||
"code": 9999
|
||||
}
|
||||
|
||||
### 请求 /logout 接口 => 成功
|
||||
POST {{appApi}}/member/logout
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer c1b76bdaf2c146c581caa4d7fd81ee66
|
||||
tenant-id: {{appTenentId}}
|
@@ -0,0 +1,121 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.auth;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
|
||||
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
|
||||
import cn.iocoder.yudao.module.member.service.auth.MemberAuthService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiImplicitParams;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Api(tags = "用户 APP - 认证")
|
||||
@RestController
|
||||
@RequestMapping("/member/")
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class AppAuthController {
|
||||
|
||||
@Resource
|
||||
private MemberAuthService authService;
|
||||
|
||||
@PostMapping("/login")
|
||||
@ApiOperation("使用手机 + 密码登录")
|
||||
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
|
||||
public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {
|
||||
String token = authService.login(reqVO, getClientIP(), getUserAgent());
|
||||
// 返回结果
|
||||
return success(AppAuthLoginRespVO.builder().token(token).build());
|
||||
}
|
||||
|
||||
@PostMapping("/sms-login")
|
||||
@ApiOperation("使用手机 + 验证码登录")
|
||||
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
|
||||
public CommonResult<AppAuthLoginRespVO> smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) {
|
||||
String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent());
|
||||
// 返回结果
|
||||
return success(AppAuthLoginRespVO.builder().token(token).build());
|
||||
}
|
||||
|
||||
@PostMapping("/send-sms-code")
|
||||
@ApiOperation(value = "发送手机验证码")
|
||||
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
|
||||
public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid AppAuthSendSmsReqVO reqVO) {
|
||||
authService.sendSmsCode(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/reset-password")
|
||||
@ApiOperation(value = "重置密码", notes = "用户忘记密码时使用")
|
||||
@PreAuthenticated
|
||||
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
|
||||
public CommonResult<Boolean> resetPassword(@RequestBody @Valid AppAuthResetPasswordReqVO reqVO) {
|
||||
authService.resetPassword(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/update-password")
|
||||
@ApiOperation(value = "修改用户密码",notes = "用户修改密码时使用")
|
||||
@PreAuthenticated
|
||||
public CommonResult<Boolean> updatePassword(@RequestBody @Valid AppAuthUpdatePasswordReqVO reqVO) {
|
||||
authService.updatePassword(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// ========== 社交登录相关 ==========
|
||||
|
||||
@GetMapping("/social-auth-redirect")
|
||||
@ApiOperation("社交授权的跳转")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "type", value = "社交类型", required = true, dataTypeClass = Integer.class),
|
||||
@ApiImplicitParam(name = "redirectUri", value = "回调路径", dataTypeClass = String.class)
|
||||
})
|
||||
public CommonResult<String> socialAuthRedirect(@RequestParam("type") Integer type,
|
||||
@RequestParam("redirectUri") String redirectUri) {
|
||||
return CommonResult.success(authService.getSocialAuthorizeUrl(type, redirectUri));
|
||||
}
|
||||
|
||||
@PostMapping("/social-login")
|
||||
@ApiOperation(value = "社交登录,使用 code 授权码", notes = "适合未登录的用户,但是社交账号已绑定用户")
|
||||
public CommonResult<AppAuthLoginRespVO> socialLogin(@RequestBody @Valid AppAuthSocialLoginReqVO reqVO) {
|
||||
String token = authService.socialLogin(reqVO, getClientIP(), getUserAgent());
|
||||
return success(AppAuthLoginRespVO.builder().token(token).build());
|
||||
}
|
||||
|
||||
@PostMapping("/social-login2")
|
||||
@ApiOperation(value = "社交登录,使用 手机号 + 手机验证码", notes = "适合未登录的用户,进行登录 + 绑定")
|
||||
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
|
||||
public CommonResult<AppAuthLoginRespVO> socialLogin2(@RequestBody @Valid AppAuthSocialLogin2ReqVO reqVO) {
|
||||
String token = authService.socialLogin2(reqVO, getClientIP(), getUserAgent());
|
||||
return success(AppAuthLoginRespVO.builder().token(token).build());
|
||||
}
|
||||
|
||||
@PostMapping("/social-bind")
|
||||
@ApiOperation(value = "社交绑定,使用 code 授权码", notes = "使用在用户已经登录的情况下")
|
||||
@PreAuthenticated
|
||||
public CommonResult<Boolean> socialBind(@RequestBody @Valid AppAuthSocialBindReqVO reqVO) {
|
||||
authService.socialBind(getLoginUserId(), reqVO);
|
||||
return CommonResult.success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/social-unbind")
|
||||
@ApiOperation("取消社交绑定")
|
||||
@PreAuthenticated
|
||||
public CommonResult<Boolean> socialUnbind(@RequestBody AppAuthSocialUnbindReqVO reqVO) {
|
||||
authService.unbindSocialUser(getLoginUserId(), reqVO);
|
||||
return CommonResult.success(true);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.framework.common.validation.Mobile;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
// TODO 芋艿:code review 相关逻辑
|
||||
@ApiModel("用户 APP - 校验验证码 Request VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AppAuthCheckCodeReqVO {
|
||||
|
||||
@ApiModelProperty(value = "手机号", example = "15601691234")
|
||||
@NotBlank(message = "手机号不能为空")
|
||||
@Mobile
|
||||
private String mobile;
|
||||
|
||||
@ApiModelProperty(value = "手机验证码", required = true, example = "1024")
|
||||
@NotBlank(message = "手机验证码不能为空")
|
||||
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
|
||||
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
|
||||
private String code;
|
||||
|
||||
@ApiModelProperty(value = "发送场景", example = "1", notes = "对应 SmsSceneEnum 枚举")
|
||||
@NotNull(message = "发送场景不能为空")
|
||||
@InEnum(SmsSceneEnum.class)
|
||||
private Integer scene;
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.Mobile;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
@ApiModel("用户 APP - 手机 + 密码登录 Request VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AppAuthLoginReqVO {
|
||||
|
||||
@ApiModelProperty(value = "手机号", required = true, example = "15601691300")
|
||||
@NotEmpty(message = "手机号不能为空")
|
||||
@Mobile
|
||||
private String mobile;
|
||||
|
||||
@ApiModelProperty(value = "密码", required = true, example = "buzhidao")
|
||||
@NotEmpty(message = "密码不能为空")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
private String password;
|
||||
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@ApiModel("用户 APP - 手机密码登录 Response VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AppAuthLoginRespVO {
|
||||
|
||||
@ApiModelProperty(value = "token", required = true, example = "yudaoyuanma")
|
||||
private String token;
|
||||
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.Mobile;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
// TODO 芋艿:code review 相关逻辑
|
||||
@ApiModel("用户 APP - 重置密码 Request VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AppAuthResetPasswordReqVO {
|
||||
|
||||
@ApiModelProperty(value = "新密码", required = true, example = "buzhidao")
|
||||
@NotEmpty(message = "新密码不能为空")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
private String password;
|
||||
|
||||
@ApiModelProperty(value = "手机验证码", required = true, example = "1024")
|
||||
@NotEmpty(message = "手机验证码不能为空")
|
||||
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
|
||||
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
|
||||
private String code;
|
||||
|
||||
@ApiModelProperty(value = "手机号",required = true,example = "15878962356")
|
||||
@NotBlank(message = "手机号不能为空")
|
||||
@Mobile
|
||||
private String mobile;
|
||||
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.framework.common.validation.Mobile;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@ApiModel("用户 APP - 发送手机验证码 Response VO")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AppAuthSendSmsReqVO {
|
||||
|
||||
@ApiModelProperty(value = "手机号", example = "15601691234")
|
||||
@Mobile
|
||||
private String mobile;
|
||||
|
||||
@ApiModelProperty(value = "发送场景", example = "1", notes = "对应 SmsSceneEnum 枚举")
|
||||
@NotNull(message = "发送场景不能为空")
|
||||
@InEnum(SmsSceneEnum.class)
|
||||
private Integer scene;
|
||||
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.Mobile;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
@ApiModel("用户 APP - 手机 + 验证码登录 Request VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AppAuthSmsLoginReqVO {
|
||||
|
||||
@ApiModelProperty(value = "手机号", required = true, example = "15601691300")
|
||||
@NotEmpty(message = "手机号不能为空")
|
||||
@Mobile
|
||||
private String mobile;
|
||||
|
||||
@ApiModelProperty(value = "手机验证码", required = true, example = "1024")
|
||||
@NotEmpty(message = "手机验证码不能为空")
|
||||
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
|
||||
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
|
||||
private String code;
|
||||
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@ApiModel("用户 APP - 社交绑定 Request VO,使用 code 授权码")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AppAuthSocialBindReqVO {
|
||||
|
||||
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
|
||||
@InEnum(SocialTypeEnum.class)
|
||||
@NotNull(message = "社交平台的类型不能为空")
|
||||
private Integer type;
|
||||
|
||||
@ApiModelProperty(value = "授权码", required = true, example = "1024")
|
||||
@NotEmpty(message = "授权码不能为空")
|
||||
private String code;
|
||||
|
||||
@ApiModelProperty(value = "state", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
|
||||
@NotEmpty(message = "state 不能为空")
|
||||
private String state;
|
||||
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
@ApiModel("用户 APP - 社交登录 Request VO,使用 code 授权码 + 账号密码")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AppAuthSocialLogin2ReqVO {
|
||||
|
||||
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
|
||||
@InEnum(SocialTypeEnum.class)
|
||||
@NotNull(message = "社交平台的类型不能为空")
|
||||
private Integer type;
|
||||
|
||||
@ApiModelProperty(value = "授权码", required = true, example = "1024")
|
||||
@NotEmpty(message = "授权码不能为空")
|
||||
private String code;
|
||||
|
||||
@ApiModelProperty(value = "state", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
|
||||
@NotEmpty(message = "state 不能为空")
|
||||
private String state;
|
||||
|
||||
@ApiModelProperty(value = "手机号", required = true, example = "15119100000")
|
||||
@NotEmpty(message = "手机号不能为空")
|
||||
@Length(min = 11, max = 11, message = "手机号是11位数字")
|
||||
private String mobile;
|
||||
|
||||
@ApiModelProperty(value = "手机验证码", required = true, example = "1024")
|
||||
@NotEmpty(message = "手机验证码不能为空")
|
||||
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
|
||||
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
|
||||
private String smsCode;
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@ApiModel("用户 APP - 社交登录 Request VO,使用 code 授权码")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AppAuthSocialLoginReqVO {
|
||||
|
||||
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
|
||||
@InEnum(SocialTypeEnum.class)
|
||||
@NotNull(message = "社交平台的类型不能为空")
|
||||
private Integer type;
|
||||
|
||||
@ApiModelProperty(value = "授权码", required = true, example = "1024")
|
||||
@NotEmpty(message = "授权码不能为空")
|
||||
private String code;
|
||||
|
||||
@ApiModelProperty(value = "state", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
|
||||
@NotEmpty(message = "state 不能为空")
|
||||
private String state;
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@ApiModel("用户 APP - 取消社交绑定 Request VO,使用 code 授权码")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AppAuthSocialUnbindReqVO {
|
||||
|
||||
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
|
||||
@InEnum(SocialTypeEnum.class)
|
||||
@NotNull(message = "社交平台的类型不能为空")
|
||||
private Integer type;
|
||||
|
||||
@ApiModelProperty(value = "社交的全局编号", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE")
|
||||
@NotEmpty(message = "社交的全局编号不能为空")
|
||||
private String unionId;
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
// TODO 芋艿:code review 相关逻辑
|
||||
@ApiModel("用户 APP - 修改密码 Request VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AppAuthUpdatePasswordReqVO {
|
||||
|
||||
@ApiModelProperty(value = "用户旧密码", required = true, example = "123456")
|
||||
@NotBlank(message = "旧密码不能为空")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
private String oldPassword;
|
||||
|
||||
@ApiModelProperty(value = "新密码", required = true, example = "buzhidao")
|
||||
@NotEmpty(message = "新密码不能为空")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
private String password;
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
### 请求 /member/user/profile/get 接口 => 没有权限
|
||||
GET {{appApi}}/member/user/get
|
||||
Authorization: Bearer test245
|
||||
tenant-id: {{appTenentId}}
|
||||
|
||||
### 请求 /member/user/profile/revise-nickname 接口 成功
|
||||
PUT {{appApi}}/member/user/update-nickname?nickname=yunai222
|
||||
Authorization: Bearer test245
|
||||
tenant-id: {{appTenentId}}
|
||||
|
||||
### 请求 /member/user/get-user-info 接口 成功
|
||||
GET {{appApi}}/member/user/get-user-info?id=245
|
||||
Authorization: Bearer test245
|
||||
tenant-id: {{appTenentId}}
|
@@ -0,0 +1,71 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.user;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
|
||||
import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserInfoRespVO;
|
||||
import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserUpdateMobileReqVO;
|
||||
import cn.iocoder.yudao.module.member.convert.user.UserConvert;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY;
|
||||
|
||||
@Api(tags = "用户 APP - 用户个人中心")
|
||||
@RestController
|
||||
@RequestMapping("/member/user")
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class AppUserController {
|
||||
|
||||
@Resource
|
||||
private MemberUserService userService;
|
||||
|
||||
@PutMapping("/update-nickname")
|
||||
@ApiOperation("修改用户昵称")
|
||||
@PreAuthenticated
|
||||
public CommonResult<Boolean> updateUserNickname(@RequestParam("nickname") String nickname) {
|
||||
userService.updateUserNickname(getLoginUserId(), nickname);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/update-avatar")
|
||||
@ApiOperation("修改用户头像")
|
||||
@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")
|
||||
@ApiOperation("获得基本信息")
|
||||
@PreAuthenticated
|
||||
public CommonResult<AppUserInfoRespVO> getUserInfo() {
|
||||
MemberUserDO user = userService.getUser(getLoginUserId());
|
||||
return success(UserConvert.INSTANCE.convert(user));
|
||||
}
|
||||
|
||||
@PostMapping("/update-mobile")
|
||||
@ApiOperation(value = "修改用户手机")
|
||||
@PreAuthenticated
|
||||
public CommonResult<Boolean> updateMobile(@RequestBody @Valid AppUserUpdateMobileReqVO reqVO) {
|
||||
userService.updateUserMobile(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,20 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.user.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@ApiModel("用户 APP - 用户个人信息 Response VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AppUserInfoRespVO {
|
||||
|
||||
@ApiModelProperty(value = "用户昵称", required = true, example = "芋艿")
|
||||
private String nickname;
|
||||
|
||||
@ApiModelProperty(value = "用户头像", required = true, example = "/infra/file/get/35a12e57-4297-4faa-bf7d-7ed2f211c952")
|
||||
private String avatar;
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.user.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.Mobile;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
@ApiModel("用户 APP - 修改手机 Request VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AppUserUpdateMobileReqVO {
|
||||
|
||||
@ApiModelProperty(value = "手机验证码", required = true, example = "1024")
|
||||
@NotEmpty(message = "手机验证码不能为空")
|
||||
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
|
||||
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
|
||||
private String code;
|
||||
|
||||
@ApiModelProperty(value = "手机号",required = true,example = "15823654487")
|
||||
@NotBlank(message = "手机号不能为空")
|
||||
@Length(min = 8, max = 11, message = "手机号码长度为 8-11 位")
|
||||
@Mobile
|
||||
private String mobile;
|
||||
|
||||
@ApiModelProperty(value = "原手机验证码", required = true, example = "1024")
|
||||
@NotEmpty(message = "原手机验证码不能为空")
|
||||
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
|
||||
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
|
||||
private String oldCode;
|
||||
|
||||
// TODO @芋艿:oldMobile 应该不用传递
|
||||
|
||||
@ApiModelProperty(value = "原手机号",required = true,example = "15823654487")
|
||||
@NotBlank(message = "手机号不能为空")
|
||||
@Length(min = 8, max = 11, message = "手机号码长度为 8-11 位")
|
||||
@Mobile
|
||||
private String oldMobile;
|
||||
|
||||
}
|
@@ -0,0 +1,2 @@
|
||||
### 请求 /login 接口 => 成功
|
||||
GET {{userServerUrl}}/wx/mp/get-jsapi-ticket
|
@@ -0,0 +1,37 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.weixin;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
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.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Api(tags = "微信公众号")
|
||||
@RestController
|
||||
@RequestMapping("/member/wx-mp")
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class AppWxMpController {
|
||||
|
||||
@Resource
|
||||
private WxMpService mpService;
|
||||
|
||||
@PostMapping("/create-jsapi-signature")
|
||||
@ApiOperation(value = "创建微信 JS SDK 初始化所需的签名",
|
||||
notes = "参考 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,6 @@
|
||||
/**
|
||||
* 提供 RESTful API 给前端:
|
||||
* 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目
|
||||
* 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分
|
||||
*/
|
||||
package cn.iocoder.yudao.module.member.controller;
|
@@ -0,0 +1,32 @@
|
||||
package cn.iocoder.yudao.module.member.convert.address;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
import cn.iocoder.yudao.module.member.controller.app.address.vo.*;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.address.AddressDO;
|
||||
|
||||
/**
|
||||
* 用户收件地址 Convert
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface AddressConvert {
|
||||
|
||||
AddressConvert INSTANCE = Mappers.getMapper(AddressConvert.class);
|
||||
|
||||
AddressDO convert(AppAddressCreateReqVO bean);
|
||||
|
||||
AddressDO convert(AppAddressUpdateReqVO bean);
|
||||
|
||||
AppAddressRespVO convert(AddressDO bean);
|
||||
|
||||
List<AppAddressRespVO> convertList(List<AddressDO> list);
|
||||
|
||||
PageResult<AppAddressRespVO> convertPage(PageResult<AddressDO> page);
|
||||
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
package cn.iocoder.yudao.module.member.convert.auth;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface AuthConvert {
|
||||
|
||||
AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class);
|
||||
|
||||
@Mapping(source = "mobile", target = "username")
|
||||
LoginUser convert0(MemberUserDO bean);
|
||||
|
||||
default LoginUser convert(MemberUserDO bean) {
|
||||
// 目的,为了设置 UserTypeEnum.MEMBER.getValue()
|
||||
return convert0(bean).setUserType(UserTypeEnum.MEMBER.getValue());
|
||||
}
|
||||
|
||||
SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindReqVO reqVO);
|
||||
SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialLogin2ReqVO reqVO);
|
||||
SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialLoginReqVO reqVO);
|
||||
SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppAuthSocialUnbindReqVO reqVO);
|
||||
|
||||
SmsCodeSendReqDTO convert(AppAuthSendSmsReqVO reqVO);
|
||||
SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp);
|
||||
SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, Integer scene, String usedIp);
|
||||
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 提供 POJO 类的实体转换
|
||||
*
|
||||
* 目前使用 MapStruct 框架
|
||||
*/
|
||||
package cn.iocoder.yudao.module.member.convert;
|
@@ -0,0 +1,17 @@
|
||||
package cn.iocoder.yudao.module.member.convert.user;
|
||||
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
|
||||
import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserInfoRespVO;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface UserConvert {
|
||||
|
||||
UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);
|
||||
|
||||
AppUserInfoRespVO convert(MemberUserDO bean);
|
||||
|
||||
UserRespDTO convert2(MemberUserDO bean);
|
||||
}
|
@@ -0,0 +1 @@
|
||||
<http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao>
|
@@ -0,0 +1,55 @@
|
||||
package cn.iocoder.yudao.module.member.dal.dataobject.address;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.module.member.enums.AddressTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 用户收件地址 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("member_address")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AddressDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 收件人名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
private String mobile;
|
||||
/**
|
||||
* 地区编码
|
||||
*/
|
||||
private Integer areaCode;
|
||||
/**
|
||||
* 收件详细地址
|
||||
*/
|
||||
private String detailAddress;
|
||||
/**
|
||||
* 地址类型
|
||||
*
|
||||
* 枚举 {@link AddressTypeEnum}
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
package cn.iocoder.yudao.module.member.dal.dataobject.user;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 会员用户 DO
|
||||
*
|
||||
* uk_mobile 索引:基于 {@link #mobile} 字段
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName(value = "member_user", autoResultMap = true)
|
||||
@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 Date loginDate;
|
||||
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
package cn.iocoder.yudao.module.member.dal.mysql.address;
|
||||
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.address.AddressDO;
|
||||
import cn.iocoder.yudao.module.member.enums.AddressTypeEnum;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户收件地址 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface AddressMapper extends BaseMapperX<AddressDO> {
|
||||
|
||||
/**
|
||||
* 获取当前地址 根据id和userId
|
||||
* @param userId
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
default AddressDO getAddressByIdAndUserId(Long userId, Long id) {
|
||||
QueryWrapperX<AddressDO> queryWrapperX = new QueryWrapperX<>();
|
||||
queryWrapperX.eq("user_id", userId).eq("id", id);
|
||||
return selectList(queryWrapperX).stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取地址列表
|
||||
* @param userId
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
default List<AddressDO> selectListByUserIdAndType(Long userId, Integer type) {
|
||||
QueryWrapperX<AddressDO> queryWrapperX = new QueryWrapperX<AddressDO>().eq("user_id", userId)
|
||||
.eqIfPresent("type", type);
|
||||
return selectList(queryWrapperX);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认地址
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
default AddressDO getDefaultUserAddress(Long userId) {
|
||||
List<AddressDO> addressDOList = selectListByUserIdAndType(userId, AddressTypeEnum.DEFAULT.getType());
|
||||
return addressDOList.stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认地址
|
||||
* @param id
|
||||
* @param addressTypeEnum
|
||||
* @return
|
||||
*/
|
||||
default int updateTypeById(Long id, AddressTypeEnum addressTypeEnum) {
|
||||
return updateById(new AddressDO().setId(id).setType(addressTypeEnum.getType()));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package cn.iocoder.yudao.module.member.dal.mysql.user;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 会员 User Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface MemberUserMapper extends BaseMapperX<MemberUserDO> {
|
||||
|
||||
default MemberUserDO selectByMobile(String mobile) {
|
||||
return selectOne(MemberUserDO::getMobile, mobile);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* DAL = Data Access Layer 数据访问层
|
||||
* 1. data object:数据对象
|
||||
* 2. redis:Redis 的 CRUD 操作
|
||||
* 3. mysql:MySQL 的 CRUD 操作
|
||||
*
|
||||
* 其中,MySQL 的表以 member_ 作为前缀
|
||||
*/
|
||||
package cn.iocoder.yudao.module.member.dal;
|
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 占位,后续有类后,可以删除,避免 package 无法提交到 Git 上
|
||||
*/
|
||||
package cn.iocoder.yudao.module.member.dal.redis;
|
@@ -0,0 +1,30 @@
|
||||
package cn.iocoder.yudao.module.member.enums;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 用户收件地址的类型枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum AddressTypeEnum implements IntArrayValuable {
|
||||
|
||||
DEFAULT(1, "默认收件地址"),
|
||||
NORMAL(2, "普通收件地址"), // 即非默认收件地址
|
||||
|
||||
;
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AddressTypeEnum::getType).toArray();
|
||||
|
||||
private final Integer type;
|
||||
private final String desc;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 占位
|
||||
*/
|
||||
package cn.iocoder.yudao.module.member.enums;
|
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* member 模块,我们放会员业务。
|
||||
* 例如说:会员中心等等
|
||||
*
|
||||
* 1. Controller URL:以 /member/ 开头,避免和其它 Module 冲突
|
||||
* 2. DataObject 表名:以 member_ 开头,方便在数据库中区分
|
||||
*/
|
||||
package cn.iocoder.yudao.module.member;
|
@@ -0,0 +1,59 @@
|
||||
package cn.iocoder.yudao.module.member.service.address;
|
||||
|
||||
import java.util.*;
|
||||
import javax.validation.*;
|
||||
import cn.iocoder.yudao.module.member.controller.app.address.vo.*;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.address.AddressDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
|
||||
/**
|
||||
* 用户收件地址 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface AddressService {
|
||||
|
||||
/**
|
||||
* 创建用户收件地址
|
||||
*
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createAddress(Long userId, @Valid AppAddressCreateReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新用户收件地址
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateAddress(Long userId, @Valid AppAddressUpdateReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除用户收件地址
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteAddress(Long userId, Long id);
|
||||
|
||||
/**
|
||||
* 获得用户收件地址
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 用户收件地址
|
||||
*/
|
||||
AddressDO getAddress(Long userId, Long id);
|
||||
|
||||
/**
|
||||
* 获得用户收件地址列表
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @return 用户收件地址列表
|
||||
*/
|
||||
List<AddressDO> getAddressList(Long userId);
|
||||
|
||||
AddressDO getDefaultUserAddress(Long userId);
|
||||
}
|
@@ -0,0 +1,133 @@
|
||||
package cn.iocoder.yudao.module.member.service.address;
|
||||
|
||||
import cn.iocoder.yudao.module.member.enums.AddressTypeEnum;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.*;
|
||||
import cn.iocoder.yudao.module.member.controller.app.address.vo.*;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.address.AddressDO;
|
||||
|
||||
import cn.iocoder.yudao.module.member.convert.address.AddressConvert;
|
||||
import cn.iocoder.yudao.module.member.dal.mysql.address.AddressMapper;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 用户收件地址 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class AddressServiceImpl implements AddressService {
|
||||
|
||||
@Resource
|
||||
private AddressMapper addressMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createAddress(Long userId, AppAddressCreateReqVO createReqVO) {
|
||||
// 如果添加的是默认收件地址,则将原默认地址修改为非默认
|
||||
if (AddressTypeEnum.DEFAULT.getType().equals(createReqVO.getType())) {
|
||||
//查询到一个,然后进行 update
|
||||
List<AddressDO> addressDOs = selectListByUserIdAndType(userId, AddressTypeEnum.DEFAULT.getType());
|
||||
AddressDO defaultUserAddress = addressMapper.getDefaultUserAddress(userId);
|
||||
if (!CollectionUtils.isEmpty(addressDOs)) {
|
||||
addressDOs.forEach(userAddressDO -> addressMapper.updateById(new AddressDO()
|
||||
.setId(userAddressDO.getId()).setType(AddressTypeEnum.NORMAL.getType())));
|
||||
}
|
||||
Optional.ofNullable(defaultUserAddress)
|
||||
//更新为非默认
|
||||
.ifPresent( u -> addressMapper.updateTypeById(u.getId(), AddressTypeEnum.NORMAL));
|
||||
}
|
||||
// 插入
|
||||
AddressDO address = AddressConvert.INSTANCE.convert(createReqVO);
|
||||
address.setUserId(userId);
|
||||
addressMapper.insert(address);
|
||||
// 返回
|
||||
return address.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateAddress(Long userId, AppAddressUpdateReqVO updateReqVO) {
|
||||
// 校验存在,校验是否能够操作
|
||||
check(userId, updateReqVO.getId());
|
||||
// 如果修改的是默认收件地址,则将原默认地址修改为非默认
|
||||
if (AddressTypeEnum.DEFAULT.getType().equals(updateReqVO.getType())) {
|
||||
//获取默认地址
|
||||
AddressDO defaultUserAddress = addressMapper.getDefaultUserAddress(userId);
|
||||
Optional.ofNullable(defaultUserAddress)
|
||||
//排除当前地址
|
||||
.filter(u -> !u.getId().equals(updateReqVO.getId()))
|
||||
//更新为非默认
|
||||
.ifPresent( u -> addressMapper.updateTypeById(u.getId(), AddressTypeEnum.NORMAL));
|
||||
}
|
||||
// 更新
|
||||
AddressDO updateObj = AddressConvert.INSTANCE.convert(updateReqVO);
|
||||
addressMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAddress(Long userId, Long id) {
|
||||
// 校验存在,校验是否能够操作
|
||||
check(userId, id);
|
||||
// 删除
|
||||
addressMapper.deleteById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验用户收件地址是不是属于该用户
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param userAddressId 用户收件地址
|
||||
*/
|
||||
private void check(Long userId, Long userAddressId) {
|
||||
AddressDO addressDO = getAddress(userId, userAddressId);
|
||||
if(null == addressDO){
|
||||
throw exception(ADDRESS_NOT_EXISTS);
|
||||
}
|
||||
if (!addressDO.getUserId().equals(userId)) {
|
||||
throw exception(ADDRESS_FORBIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressDO getAddress(Long userId, Long id) {
|
||||
return addressMapper.getAddressByIdAndUserId(userId, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AddressDO> getAddressList(Long userId) {
|
||||
return selectListByUserIdAndType(userId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认地址
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public AddressDO getDefaultUserAddress(Long userId) {
|
||||
return addressMapper.getDefaultUserAddress(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据类型获取地址列表
|
||||
* @param userId
|
||||
* @param type null则查询全部
|
||||
* @return
|
||||
*/
|
||||
public List<AddressDO> selectListByUserIdAndType(Long userId, Integer type) {
|
||||
return addressMapper.selectListByUserIdAndType(userId, type);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,104 @@
|
||||
package cn.iocoder.yudao.module.member.service.auth;
|
||||
|
||||
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
|
||||
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* 会员的认证 Service 接口
|
||||
*
|
||||
* 提供用户的账号密码登录、token 的校验等认证相关的功能
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface MemberAuthService extends SecurityAuthFrameworkService {
|
||||
|
||||
/**
|
||||
* 手机 + 密码登录
|
||||
*
|
||||
* @param reqVO 登录信息
|
||||
* @param userIp 用户 IP
|
||||
* @param userAgent 用户 UA
|
||||
* @return 身份令牌,使用 JWT 方式
|
||||
*/
|
||||
String login(@Valid AppAuthLoginReqVO reqVO, String userIp, String userAgent);
|
||||
|
||||
/**
|
||||
* 手机 + 验证码登陆
|
||||
*
|
||||
* @param reqVO 登陆信息
|
||||
* @param userIp 用户 IP
|
||||
* @param userAgent 用户 UA
|
||||
* @return 身份令牌,使用 JWT 方式
|
||||
*/
|
||||
String smsLogin(@Valid AppAuthSmsLoginReqVO reqVO, String userIp, String userAgent);
|
||||
|
||||
|
||||
/**
|
||||
* 社交登录,使用 code 授权码
|
||||
*
|
||||
* @param reqVO 登录信息
|
||||
* @param userIp 用户 IP
|
||||
* @param userAgent 用户 UA
|
||||
* @return 身份令牌,使用 JWT 方式
|
||||
*/
|
||||
String socialLogin(@Valid AppAuthSocialLoginReqVO reqVO, String userIp, String userAgent);
|
||||
|
||||
/**
|
||||
* 社交登录,使用 手机号 + 手机验证码
|
||||
*
|
||||
* @param reqVO 登录信息
|
||||
* @param userIp 用户 IP
|
||||
* @param userAgent 用户 UA
|
||||
* @return 身份令牌,使用 JWT 方式
|
||||
*/
|
||||
String socialLogin2(@Valid AppAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent);
|
||||
|
||||
/**
|
||||
* 社交绑定,使用 code 授权码
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param reqVO 绑定信息
|
||||
*/
|
||||
void socialBind(Long userId, @Valid AppAuthSocialBindReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 取消社交绑定
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param reqVO 解绑信息
|
||||
*/
|
||||
void unbindSocialUser(Long userId, @Valid AppAuthSocialUnbindReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 获得社交认证 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, AppAuthSendSmsReqVO reqVO);
|
||||
|
||||
}
|
@@ -0,0 +1,354 @@
|
||||
package cn.iocoder.yudao.module.member.service.auth;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
|
||||
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
|
||||
import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||
import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
|
||||
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
|
||||
import cn.iocoder.yudao.module.system.api.auth.UserSessionApi;
|
||||
import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
|
||||
import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
|
||||
import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
|
||||
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 会员的认证 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class MemberAuthServiceImpl implements MemberAuthService {
|
||||
|
||||
@Resource
|
||||
@Lazy // 延迟加载,因为存在相互依赖的问题
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
@Resource
|
||||
private MemberUserService userService;
|
||||
@Resource
|
||||
private SmsCodeApi smsCodeApi;
|
||||
@Resource
|
||||
private LoginLogApi loginLogApi;
|
||||
@Resource
|
||||
private UserSessionApi userSessionApi;
|
||||
@Resource
|
||||
private SocialUserApi socialUserApi;
|
||||
|
||||
@Resource
|
||||
private PasswordEncoder passwordEncoder;
|
||||
@Resource
|
||||
private MemberUserMapper userMapper;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
|
||||
// 获取 username 对应的 SysUserDO
|
||||
MemberUserDO user = userService.getUserByMobile(mobile);
|
||||
if (user == null) {
|
||||
throw new UsernameNotFoundException(mobile);
|
||||
}
|
||||
// 创建 LoginUser 对象
|
||||
return AuthConvert.INSTANCE.convert(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String login(AppAuthLoginReqVO reqVO, String userIp, String userAgent) {
|
||||
// 使用手机 + 密码,进行登录。
|
||||
LoginUser loginUser = this.login0(reqVO.getMobile(), reqVO.getPassword());
|
||||
|
||||
// 缓存登录用户到 Redis 中,返回 sessionId 编号
|
||||
return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public String smsLogin(AppAuthSmsLoginReqVO reqVO, String userIp, String userAgent) {
|
||||
// 校验验证码
|
||||
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_LOGIN.getScene(), userIp));
|
||||
|
||||
// 获得获得注册用户
|
||||
MemberUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), userIp);
|
||||
Assert.notNull(user, "获取用户失败,结果为空");
|
||||
|
||||
// 执行登陆
|
||||
LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
|
||||
|
||||
// 缓存登录用户到 Redis 中,返回 sessionId 编号
|
||||
return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SMS, userIp, userAgent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String socialLogin(AppAuthSocialLoginReqVO reqVO, String userIp, String userAgent) {
|
||||
// 使用 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);
|
||||
}
|
||||
|
||||
// 创建 LoginUser 对象
|
||||
LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
|
||||
|
||||
// 绑定社交用户(更新)
|
||||
socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
|
||||
|
||||
// 缓存登录用户到 Redis 中,返回 sessionId 编号
|
||||
return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String socialLogin2(AppAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent) {
|
||||
// 校验社交平台的认证信息是否正确
|
||||
socialUserApi.checkSocialUser(reqVO.getType(), reqVO.getCode(), reqVO.getState());
|
||||
|
||||
// 使用手机号、手机验证码登录
|
||||
AppAuthSmsLoginReqVO loginReqVO = AppAuthSmsLoginReqVO.builder()
|
||||
.mobile(reqVO.getMobile()).code(reqVO.getSmsCode()).build();
|
||||
String sessionId = this.smsLogin(loginReqVO, userIp, userAgent);
|
||||
LoginUser loginUser = userSessionApi.getLoginUser(sessionId);
|
||||
|
||||
// 绑定社交用户(新增)
|
||||
socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
private String createUserSessionAfterLoginSuccess(LoginUser loginUser, LoginLogTypeEnum logType, String userIp, String userAgent) {
|
||||
// 插入登陆日志
|
||||
createLoginLog(loginUser.getUsername(), logType, LoginResultEnum.SUCCESS);
|
||||
// 缓存登录用户到 Redis 中,返回 sessionId 编号
|
||||
return userSessionApi.createUserSession(loginUser, userIp, userAgent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void socialBind(Long userId, AppAuthSocialBindReqVO reqVO) {
|
||||
// 绑定社交用户(新增)
|
||||
socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(userId, getUserType().getValue(), reqVO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindSocialUser(Long userId, AppAuthSocialUnbindReqVO reqVO) {
|
||||
socialUserApi.unbindSocialUser(AuthConvert.INSTANCE.convert(userId, getUserType().getValue(), reqVO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSocialAuthorizeUrl(Integer type, String redirectUri) {
|
||||
return socialUserApi.getAuthorizeUrl(type, redirectUri);
|
||||
}
|
||||
|
||||
private LoginUser login0(String username, String password) {
|
||||
final LoginLogTypeEnum logType = LoginLogTypeEnum.LOGIN_USERNAME;
|
||||
// 用户验证
|
||||
Authentication authentication;
|
||||
try {
|
||||
// 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证
|
||||
// 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息
|
||||
authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken(
|
||||
username, password, getUserType()));
|
||||
} catch (BadCredentialsException badCredentialsException) {
|
||||
this.createLoginLog(username, logType, LoginResultEnum.BAD_CREDENTIALS);
|
||||
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
|
||||
} catch (DisabledException disabledException) {
|
||||
this.createLoginLog(username, logType, LoginResultEnum.USER_DISABLED);
|
||||
throw exception(AUTH_LOGIN_USER_DISABLED);
|
||||
} catch (AuthenticationException authenticationException) {
|
||||
log.error("[login0][username({}) 发生未知异常]", username, authenticationException);
|
||||
this.createLoginLog(username, logType, LoginResultEnum.UNKNOWN_ERROR);
|
||||
throw exception(AUTH_LOGIN_FAIL_UNKNOWN);
|
||||
}
|
||||
Assert.notNull(authentication.getPrincipal(), "Principal 不会为空");
|
||||
return (LoginUser) authentication.getPrincipal();
|
||||
}
|
||||
|
||||
private void createLoginLog(String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) {
|
||||
// 获得用户
|
||||
MemberUserDO user = userService.getUserByMobile(mobile);
|
||||
// 插入登录日志
|
||||
LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
|
||||
reqDTO.setLogType(logType.getType());
|
||||
reqDTO.setTraceId(TracerUtils.getTraceId());
|
||||
if (user != null) {
|
||||
reqDTO.setUserId(user.getId());
|
||||
}
|
||||
reqDTO.setUserType(getUserType().getValue());
|
||||
reqDTO.setUsername(mobile);
|
||||
reqDTO.setUserAgent(ServletUtils.getUserAgent());
|
||||
reqDTO.setUserIp(getClientIP());
|
||||
reqDTO.setResult(loginResult.getResult());
|
||||
loginLogApi.createLoginLog(reqDTO);
|
||||
// 更新最后登录时间
|
||||
if (user != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
|
||||
userService.updateUserLogin(user.getId(), getClientIP());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginUser verifyTokenAndRefresh(String token) {
|
||||
// 获得 LoginUser
|
||||
LoginUser loginUser = userSessionApi.getLoginUser(token);
|
||||
if (loginUser == null) {
|
||||
return null;
|
||||
}
|
||||
// 刷新 LoginUser 缓存
|
||||
this.refreshLoginUserCache(token, loginUser);
|
||||
return loginUser;
|
||||
}
|
||||
|
||||
private void refreshLoginUserCache(String token, LoginUser loginUser) {
|
||||
// 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存
|
||||
if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() <
|
||||
userSessionApi.getSessionTimeoutMillis() / 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 重新加载 UserDO 信息
|
||||
MemberUserDO user = userService.getUser(loginUser.getId());
|
||||
if (user == null || CommonStatusEnum.DISABLE.getStatus().equals(user.getStatus())) {
|
||||
// 校验 token 时,用户被禁用的情况下,也认为 token 过期,方便前端跳转到登录界面
|
||||
throw exception(AUTH_TOKEN_EXPIRED);
|
||||
}
|
||||
|
||||
// 刷新 LoginUser 缓存
|
||||
userSessionApi.refreshUserSession(token, loginUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginUser mockLogin(Long userId) {
|
||||
// 获取用户编号对应的 UserDO
|
||||
MemberUserDO user = userService.getUser(userId);
|
||||
if (user == null) {
|
||||
throw new UsernameNotFoundException(String.valueOf(userId));
|
||||
}
|
||||
|
||||
// 执行登陆
|
||||
this.createLoginLog(user.getMobile(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS);
|
||||
|
||||
// 创建 LoginUser 对象
|
||||
return AuthConvert.INSTANCE.convert(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(String token) {
|
||||
// 查询用户信息
|
||||
LoginUser loginUser = userSessionApi.getLoginUser(token);
|
||||
if (loginUser == null) {
|
||||
return;
|
||||
}
|
||||
// 删除 session
|
||||
userSessionApi.deleteUserSession(token);
|
||||
// 记录登出日志
|
||||
this.createLogoutLog(loginUser.getId(), loginUser.getUsername());
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserTypeEnum getUserType() {
|
||||
return UserTypeEnum.MEMBER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePassword(Long userId, AppAuthUpdatePasswordReqVO reqVO) {
|
||||
// 检验旧密码
|
||||
MemberUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword());
|
||||
|
||||
// 更新用户密码
|
||||
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, AppAuthSendSmsReqVO reqVO) {
|
||||
// TODO 要根据不同的场景,校验是否有用户
|
||||
smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验旧密码
|
||||
*
|
||||
* @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, String username) {
|
||||
LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
|
||||
reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType());
|
||||
reqDTO.setTraceId(TracerUtils.getTraceId());
|
||||
reqDTO.setUserId(userId);
|
||||
reqDTO.setUserType(getUserType().getValue());
|
||||
reqDTO.setUsername(username);
|
||||
reqDTO.setUserAgent(ServletUtils.getUserAgent());
|
||||
reqDTO.setUserIp(getClientIP());
|
||||
reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
|
||||
loginLogApi.createLoginLog(reqDTO);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
package cn.iocoder.yudao.module.member.service.user;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.Mobile;
|
||||
import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserUpdateMobileReqVO;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 会员用户 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface MemberUserService {
|
||||
|
||||
/**
|
||||
* 通过手机查询用户
|
||||
*
|
||||
* @param mobile 手机
|
||||
* @return 用户对象
|
||||
*/
|
||||
MemberUserDO getUserByMobile(String mobile);
|
||||
|
||||
/**
|
||||
* 基于手机号创建用户。
|
||||
* 如果用户已经存在,则直接进行返回
|
||||
*
|
||||
* @param mobile 手机号
|
||||
* @param registerIp 注册 IP
|
||||
* @return 用户对象
|
||||
*/
|
||||
MemberUserDO createUserIfAbsent(@Mobile String mobile, String registerIp);
|
||||
|
||||
/**
|
||||
* 更新用户的最后登陆信息
|
||||
*
|
||||
* @param id 用户编号
|
||||
* @param loginIp 登陆 IP
|
||||
*/
|
||||
void updateUserLogin(Long id, String loginIp);
|
||||
|
||||
/**
|
||||
* 通过用户 ID 查询用户
|
||||
*
|
||||
* @param id 用户ID
|
||||
* @return 用户对象信息
|
||||
*/
|
||||
MemberUserDO getUser(Long id);
|
||||
|
||||
/**
|
||||
* 修改用户昵称
|
||||
* @param userId 用户id
|
||||
* @param nickname 用户新昵称
|
||||
*/
|
||||
void updateUserNickname(Long userId, String nickname);
|
||||
|
||||
/**
|
||||
* 修改用户头像
|
||||
* @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);
|
||||
|
||||
}
|
@@ -0,0 +1,142 @@
|
||||
package cn.iocoder.yudao.module.member.service.user;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.module.infra.api.file.FileApi;
|
||||
import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserUpdateMobileReqVO;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||
import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
|
||||
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.USER_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* 会员 User Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Valid
|
||||
@Slf4j
|
||||
public class MemberUserServiceImpl implements MemberUserService {
|
||||
|
||||
@Resource
|
||||
private MemberUserMapper memberUserMapper;
|
||||
|
||||
@Resource
|
||||
private FileApi fileApi;
|
||||
@Resource
|
||||
private SmsCodeApi smsCodeApi;
|
||||
|
||||
@Resource
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
public MemberUserDO getUserByMobile(String mobile) {
|
||||
return memberUserMapper.selectByMobile(mobile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemberUserDO createUserIfAbsent(String mobile, String registerIp) {
|
||||
// 用户已经存在
|
||||
MemberUserDO user = memberUserMapper.selectByMobile(mobile);
|
||||
if (user != null) {
|
||||
return user;
|
||||
}
|
||||
// 用户不存在,则进行创建
|
||||
return this.createUser(mobile, registerIp);
|
||||
}
|
||||
|
||||
private MemberUserDO createUser(String mobile, String registerIp) {
|
||||
// 生成密码
|
||||
String password = IdUtil.fastSimpleUUID();
|
||||
// 插入用户
|
||||
MemberUserDO user = new MemberUserDO();
|
||||
user.setMobile(mobile);
|
||||
user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
|
||||
user.setPassword(passwordEncoder.encode(password)); // 加密密码
|
||||
user.setRegisterIp(registerIp);
|
||||
memberUserMapper.insert(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUserLogin(Long id, String loginIp) {
|
||||
memberUserMapper.updateById(new MemberUserDO().setId(id)
|
||||
.setLoginIp(loginIp).setLoginDate(new Date()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemberUserDO getUser(Long id) {
|
||||
return memberUserMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUserNickname(Long userId, String nickname) {
|
||||
MemberUserDO user = this.checkUserExists(userId);
|
||||
// 仅当新昵称不等于旧昵称时进行修改
|
||||
if (nickname.equals(user.getNickname())){
|
||||
return;
|
||||
}
|
||||
MemberUserDO userDO = new MemberUserDO();
|
||||
userDO.setId(user.getId());
|
||||
userDO.setNickname(nickname);
|
||||
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 芋艿: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());
|
||||
}
|
||||
|
||||
@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,160 @@
|
||||
package cn.iocoder.yudao.module.member.service.address;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressCreateReqVO;
|
||||
import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.address.AddressDO;
|
||||
import cn.iocoder.yudao.module.member.dal.mysql.address.AddressMapper;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.ADDRESS_NOT_EXISTS;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* {@link AddressServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(AddressServiceImpl.class)
|
||||
public class AddressServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private AddressServiceImpl addressService;
|
||||
|
||||
@Resource
|
||||
private AddressMapper addressMapper;
|
||||
|
||||
@Test
|
||||
public void testCreateAddress_success() {
|
||||
// 准备参数
|
||||
AppAddressCreateReqVO reqVO = randomPojo(AppAddressCreateReqVO.class);
|
||||
|
||||
// 调用
|
||||
Long addressId = addressService.createAddress(getLoginUserId(), reqVO);
|
||||
// 断言
|
||||
assertNotNull(addressId);
|
||||
// 校验记录的属性是否正确
|
||||
AddressDO address = addressMapper.selectById(addressId);
|
||||
assertPojoEquals(reqVO, address);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateAddress_success() {
|
||||
// mock 数据
|
||||
AddressDO dbAddress = randomPojo(AddressDO.class);
|
||||
addressMapper.insert(dbAddress);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
AppAddressUpdateReqVO reqVO = randomPojo(AppAddressUpdateReqVO.class, o -> {
|
||||
o.setId(dbAddress.getId()); // 设置更新的 ID
|
||||
});
|
||||
|
||||
// 调用
|
||||
addressService.updateAddress(getLoginUserId(), reqVO);
|
||||
// 校验是否更新正确
|
||||
AddressDO address = addressMapper.selectById(reqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(reqVO, address);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateAddress_notExists() {
|
||||
// 准备参数
|
||||
AppAddressUpdateReqVO reqVO = randomPojo(AppAddressUpdateReqVO.class);
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> addressService.updateAddress(getLoginUserId(), reqVO), ADDRESS_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteAddress_success() {
|
||||
// mock 数据
|
||||
AddressDO dbAddress = randomPojo(AddressDO.class);
|
||||
addressMapper.insert(dbAddress);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
Long id = dbAddress.getId();
|
||||
|
||||
// 调用
|
||||
addressService.deleteAddress(getLoginUserId(), id);
|
||||
// 校验数据不存在了
|
||||
assertNull(addressMapper.selectById(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteAddress_notExists() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> addressService.deleteAddress(getLoginUserId(), id), ADDRESS_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
|
||||
public void ins() {
|
||||
// mock 数据
|
||||
AddressDO dbAddress = randomPojo(AddressDO.class, o -> { // 等会查询到
|
||||
o.setUserId(null);
|
||||
o.setName(null);
|
||||
o.setMobile(null);
|
||||
o.setAreaCode(null);
|
||||
o.setDetailAddress(null);
|
||||
o.setType(null);
|
||||
o.setCreateTime(null);
|
||||
});
|
||||
addressMapper.insert(dbAddress);
|
||||
// 测试 userId 不匹配
|
||||
addressMapper.insert(cloneIgnoreId(dbAddress, o -> o.setUserId(null)));
|
||||
// 测试 name 不匹配
|
||||
addressMapper.insert(cloneIgnoreId(dbAddress, o -> o.setName(null)));
|
||||
// 测试 mobile 不匹配
|
||||
addressMapper.insert(cloneIgnoreId(dbAddress, o -> o.setMobile(null)));
|
||||
// 测试 areaCode 不匹配
|
||||
addressMapper.insert(cloneIgnoreId(dbAddress, o -> o.setAreaCode(null)));
|
||||
// 测试 detailAddress 不匹配
|
||||
addressMapper.insert(cloneIgnoreId(dbAddress, o -> o.setDetailAddress(null)));
|
||||
// 测试 type 不匹配
|
||||
addressMapper.insert(cloneIgnoreId(dbAddress, o -> o.setType(null)));
|
||||
// 测试 createTime 不匹配
|
||||
addressMapper.insert(cloneIgnoreId(dbAddress, o -> o.setCreateTime(null)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
|
||||
public void testGetAddressList() {
|
||||
// mock 数据
|
||||
AddressDO dbAddress = randomPojo(AddressDO.class, o -> { // 等会查询到
|
||||
o.setUserId(null);
|
||||
o.setName(null);
|
||||
o.setMobile(null);
|
||||
o.setAreaCode(null);
|
||||
o.setDetailAddress(null);
|
||||
o.setType(null);
|
||||
o.setCreateTime(null);
|
||||
});
|
||||
addressMapper.insert(dbAddress);
|
||||
// 测试 userId 不匹配
|
||||
addressMapper.insert(cloneIgnoreId(dbAddress, o -> o.setUserId(null)));
|
||||
// 测试 name 不匹配
|
||||
addressMapper.insert(cloneIgnoreId(dbAddress, o -> o.setName(null)));
|
||||
// 测试 mobile 不匹配
|
||||
addressMapper.insert(cloneIgnoreId(dbAddress, o -> o.setMobile(null)));
|
||||
// 测试 areaCode 不匹配
|
||||
addressMapper.insert(cloneIgnoreId(dbAddress, o -> o.setAreaCode(null)));
|
||||
// 测试 detailAddress 不匹配
|
||||
addressMapper.insert(cloneIgnoreId(dbAddress, o -> o.setDetailAddress(null)));
|
||||
// 测试 type 不匹配
|
||||
addressMapper.insert(cloneIgnoreId(dbAddress, o -> o.setType(null)));
|
||||
// 测试 createTime 不匹配
|
||||
addressMapper.insert(cloneIgnoreId(dbAddress, o -> o.setCreateTime(null)));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,122 @@
|
||||
package cn.iocoder.yudao.module.member.service.auth;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
||||
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
|
||||
import cn.iocoder.yudao.module.member.controller.app.auth.vo.AppAuthResetPasswordReqVO;
|
||||
import cn.iocoder.yudao.module.member.controller.app.auth.vo.AppAuthUpdatePasswordReqVO;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||
import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
|
||||
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
|
||||
import cn.iocoder.yudao.module.system.api.auth.UserSessionApi;
|
||||
import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
|
||||
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
|
||||
import cn.iocoder.yudao.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.authentication.AuthenticationManager;
|
||||
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 cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
// TODO @芋艿:单测的 review,等逻辑都达成一致后
|
||||
/**
|
||||
* {@link MemberAuthService} 的单元测试类
|
||||
*
|
||||
* @author 宋天
|
||||
*/
|
||||
@Import({MemberAuthServiceImpl.class, YudaoRedisAutoConfiguration.class})
|
||||
public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest {
|
||||
|
||||
@MockBean
|
||||
private AuthenticationManager authenticationManager;
|
||||
@MockBean
|
||||
private MemberUserService userService;
|
||||
@MockBean
|
||||
private SmsCodeApi smsCodeApi;
|
||||
@MockBean
|
||||
private LoginLogApi loginLogApi;
|
||||
@MockBean
|
||||
private UserSessionApi userSessionApi;
|
||||
@MockBean
|
||||
private SocialUserApi socialUserApi;
|
||||
@MockBean
|
||||
private PasswordEncoder passwordEncoder;
|
||||
@Resource
|
||||
private MemberUserMapper memberUserMapper;
|
||||
@Resource
|
||||
private MemberAuthServiceImpl authService;
|
||||
|
||||
@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,137 @@
|
||||
package cn.iocoder.yudao.module.member.service.user;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
||||
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
|
||||
import cn.iocoder.yudao.module.infra.api.file.FileApi;
|
||||
import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserUpdateMobileReqVO;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||
import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
|
||||
import cn.iocoder.yudao.module.member.service.auth.MemberAuthServiceImpl;
|
||||
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static cn.hutool.core.util.RandomUtil.*;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
// TODO @芋艿:单测的 review,等逻辑都达成一致后
|
||||
/**
|
||||
* {@link MemberUserServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 宋天
|
||||
*/
|
||||
@Import({MemberUserServiceImpl.class, YudaoRedisAutoConfiguration.class})
|
||||
public class MemberUserServiceImplTest extends BaseDbAndRedisUnitTest {
|
||||
|
||||
@Resource
|
||||
private MemberUserServiceImpl memberUserService;
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Resource
|
||||
private MemberUserMapper userMapper;
|
||||
|
||||
@MockBean
|
||||
private MemberAuthServiceImpl authService;
|
||||
|
||||
@MockBean
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@MockBean
|
||||
private SmsCodeApi smsCodeApi;
|
||||
@MockBean
|
||||
private FileApi fileApi;
|
||||
|
||||
@Test
|
||||
public void testUpdateNickName_success(){
|
||||
// mock 数据
|
||||
MemberUserDO userDO = randomUserDO();
|
||||
userMapper.insert(userDO);
|
||||
|
||||
// 随机昵称
|
||||
String newNickName = randomString();
|
||||
|
||||
// 调用接口修改昵称
|
||||
memberUserService.updateUserNickname(userDO.getId(),newNickName);
|
||||
// 查询新修改后的昵称
|
||||
String nickname = memberUserService.getUser(userDO.getId()).getNickname();
|
||||
// 断言
|
||||
assertEquals(newNickName,nickname);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateAvatar_success() throws Exception {
|
||||
// mock 数据
|
||||
MemberUserDO dbUser = randomUserDO();
|
||||
userMapper.insert(dbUser);
|
||||
|
||||
// 准备参数
|
||||
Long userId = dbUser.getId();
|
||||
byte[] avatarFileBytes = randomBytes(10);
|
||||
ByteArrayInputStream avatarFile = new ByteArrayInputStream(avatarFileBytes);
|
||||
// mock 方法
|
||||
String avatar = randomString();
|
||||
when(fileApi.createFile(eq(avatarFileBytes))).thenReturn(avatar);
|
||||
// 调用
|
||||
String str = memberUserService.updateUserAvatar(userId, avatarFile);
|
||||
// 断言
|
||||
assertEquals(avatar, str);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateMobile_success(){
|
||||
// mock数据
|
||||
String oldMobile = randomNumbers(11);
|
||||
MemberUserDO userDO = randomUserDO();
|
||||
userDO.setMobile(oldMobile);
|
||||
userMapper.insert(userDO);
|
||||
|
||||
// TODO 芋艿:需要修复该单元测试,重构多模块带来的
|
||||
// 旧手机和旧验证码
|
||||
// SmsCodeDO codeDO = new SmsCodeDO();
|
||||
String oldCode = RandomUtil.randomString(4);
|
||||
// codeDO.setMobile(userDO.getMobile());
|
||||
// codeDO.setCode(oldCode);
|
||||
// codeDO.setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene());
|
||||
// codeDO.setUsed(Boolean.FALSE);
|
||||
// when(smsCodeService.checkCodeIsExpired(codeDO.getMobile(),codeDO.getCode(),codeDO.getScene())).thenReturn(codeDO);
|
||||
|
||||
// 更新手机号
|
||||
String newMobile = randomNumbers(11);
|
||||
String newCode = randomNumbers(4);
|
||||
AppUserUpdateMobileReqVO reqVO = new AppUserUpdateMobileReqVO();
|
||||
reqVO.setMobile(newMobile);
|
||||
reqVO.setCode(newCode);
|
||||
reqVO.setOldMobile(oldMobile);
|
||||
reqVO.setOldCode(oldCode);
|
||||
memberUserService.updateUserMobile(userDO.getId(),reqVO);
|
||||
|
||||
assertEquals(memberUserService.getUser(userDO.getId()).getMobile(),newMobile);
|
||||
}
|
||||
|
||||
// ========== 随机对象 ==========
|
||||
|
||||
@SafeVarargs
|
||||
private static MemberUserDO randomUserDO(Consumer<MemberUserDO>... consumers) {
|
||||
Consumer<MemberUserDO> consumer = (o) -> {
|
||||
o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
|
||||
};
|
||||
return randomPojo(MemberUserDO.class, ArrayUtils.append(consumer, consumers));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
spring:
|
||||
main:
|
||||
lazy-initialization: true # 开启懒加载,加快速度
|
||||
banner-mode: off # 单元测试,禁用 Banner
|
||||
|
||||
--- #################### 数据库相关配置 ####################
|
||||
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
name: ruoyi-vue-pro
|
||||
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # 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 配置项
|
||||
|
||||
--- #################### 监控相关配置 ####################
|
||||
|
||||
--- #################### 芋道相关配置 ####################
|
||||
|
||||
# 芋道配置项,设置当前项目所有自定义的配置
|
||||
yudao:
|
||||
info:
|
||||
base-package: cn.iocoder.yudao.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";
|
@@ -0,0 +1,49 @@
|
||||
CREATE TABLE IF NOT EXISTS "member_user" (
|
||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号',
|
||||
"nickname" varchar(30) NOT NULL DEFAULT '' COMMENT '用户昵称',
|
||||
"avatar" varchar(255) NOT NULL DEFAULT '' COMMENT '头像',
|
||||
"status" tinyint NOT NULL COMMENT '状态',
|
||||
"mobile" varchar(11) NOT NULL COMMENT '手机号',
|
||||
"password" varchar(100) NOT NULL DEFAULT '' COMMENT '密码',
|
||||
"register_ip" varchar(32) NOT NULL COMMENT '注册 IP',
|
||||
"login_ip" varchar(50) NULL DEFAULT '' COMMENT '最后登录IP',
|
||||
"login_date" datetime NULL DEFAULT NULL COMMENT '最后登录时间',
|
||||
"creator" varchar(64) NULL DEFAULT '' COMMENT '创建者',
|
||||
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
"updater" varchar(64) NULL DEFAULT '' COMMENT '更新者',
|
||||
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
"deleted" bit(1) NOT NULL DEFAULT '0' COMMENT '是否删除',
|
||||
"tenant_id" bigint not null default '0',
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT '会员表';
|
||||
|
||||
-- inf 开头的 DB
|
||||
CREATE TABLE IF NOT EXISTS "inf_file" (
|
||||
"id" varchar(188) NOT NULL,
|
||||
"type" varchar(63) DEFAULT NULL,
|
||||
"content" blob NOT NULL,
|
||||
"creator" varchar(64) DEFAULT '',
|
||||
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updater" varchar(64) DEFAULT '',
|
||||
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted" bit NOT NULL DEFAULT FALSE,
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT '文件表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "member_address" (
|
||||
"id" bigint(20) NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
"user_id" bigint(20) NOT NULL,
|
||||
"name" varchar(10) NOT NULL,
|
||||
"mobile" varchar(20) NOT NULL,
|
||||
"area_code" int(11) NOT NULL,
|
||||
"detail_address" varchar(250) NOT NULL,
|
||||
"type" tinyint(4) NOT NULL,
|
||||
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"creator" varchar(64) DEFAULT '',
|
||||
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
"deleted" bit NOT NULL DEFAULT FALSE,
|
||||
"updater" varchar(64) DEFAULT '',
|
||||
"tenant_id" bigint(20) NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT '用户收件地址';
|
||||
|
Reference in New Issue
Block a user