文章目录
- 前言
- [一、实现 C 端用户服务](#一、实现 C 端用户服务)
-
- [1.1 C 端用户代码拆分](#1.1 C 端用户代码拆分)
- [1.2 定义 admin 中的 Feign 客户端](#1.2 定义 admin 中的 Feign 客户端)
-
- [1.2.1 在 admin-api 在创建 appuser 包并创建domain、feign包](#1.2.1 在 admin-api 在创建 appuser 包并创建domain、feign包)
- [1.2.2 在 domain 下创建 portal 模块需要的dto、vo](#1.2.2 在 domain 下创建 portal 模块需要的dto、vo)
- [1.2.3 在 feign 下创建 FeignClient](#1.2.3 在 feign 下创建 FeignClient)
- [1.3 创建 portal 模块并实现](#1.3 创建 portal 模块并实现)
-
- [1.3.1 在 frameworkjava 下创建 portal 模块](#1.3.1 在 frameworkjava 下创建 portal 模块)
- [1.3.2 创建 portal-api 和portal-service 包](#1.3.2 创建 portal-api 和portal-service 包)
- [1.3.3 在 portal-service 下创建启动类、包路径和 user 包](#1.3.3 在 portal-service 下创建启动类、包路径和 user 包)
- [1.3.4 添加 maven 依赖](#1.3.4 添加 maven 依赖)
- [1.3.5 配置 bootstrap.yml](#1.3.5 配置 bootstrap.yml)
- [1.3.6 在 user 下创建 controller、service、domain包](#1.3.6 在 user 下创建 controller、service、domain包)
- [1.3.7 在 domain 下定义 dto、vo](#1.3.7 在 domain 下定义 dto、vo)
- [1.3.8 在 service 下创建接口及其实现类](#1.3.8 在 service 下创建接口及其实现类)
- [1.3.9 在controller 下创建控制器](#1.3.9 在controller 下创建控制器)
- [1.4 在 admin-service 下提供 CRUD 等服务](#1.4 在 admin-service 下提供 CRUD 等服务)
-
- [1.4.1 在 user 包的 domain 下添加 C端用户的dto、vo、entity](#1.4.1 在 user 包的 domain 下添加 C端用户的dto、vo、entity)
- [1.4.2 在 mapper 下创建对应数据表的接口](#1.4.2 在 mapper 下创建对应数据表的接口)
- [1.4.3 在 service 下创建接口及其实现类](#1.4.3 在 service 下创建接口及其实现类)
- [1.4.4 在 controller 下实现远程调用的控制器](#1.4.4 在 controller 下实现远程调用的控制器)
- [二、添加 nacos 配置](#二、添加 nacos 配置)
- 三、测试
- END
鸡汤:
● 能准时下班、买到热乎的包子、路上遇到摇尾巴的狗------这些'不值一提的幸运',加起来就是生活偷偷给你的糖。
● 月亮只有一个,但星星有亿万颗。星空之所以动人,是因为每颗星都在自己的位置 quietly shining。
前言
前面封装了 B 端用户的服务,现在来实现 C 端用户的服务。
一、实现 C 端用户服务
1.1 C 端用户代码拆分
● 因为 C 端用户行为多、流量大,并且 C 端用户行为大部分依赖于应用端(移动端、小程序等)操作,因此这部分我们将放在 portal 模块 中去。

● 根据一般C/B端项目可知,管理端后台可查看管理应用端用户信息,因此,应用端用户的管理(CRUD)应放在 admin模块 中。

● 可以得出,portal模块提供应用端需要的C端用户行为接口,具体能力的实现则依 admin模块 提供的服务(例如CRUD),不同模块之间的调用则可通过 Feign 接口完成。

1.2 定义 admin 中的 Feign 客户端
1.2.1 在 admin-api 在创建 appuser 包并创建domain、feign包

1.2.2 在 domain 下创建 portal 模块需要的dto、vo

dto:
AppUserListReqDTO:
java
package com.my.adminapi.appuser.domain.dto;
import com.my.commondomain.domain.dto.BasePageReqDTO;
import lombok.Data;
import java.io.Serializable;
@Data
public class AppUserListReqDTO extends BasePageReqDTO implements Serializable {
/**
* 用户ID
*/
private Long userId;
/**
* 手机号
*/
private String phoneNumber;
/**
* 昵称
*/
private String nickName;
/**
* 微信openId
*/
private String openId;
}
UserEditReqDTO:
java
package com.my.adminapi.appuser.domain.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serializable;
/**
* C端用户编辑接口 DTO
*/
@Data
public class UserEditReqDTO implements Serializable {
/**
* 用户ID
*/
@NotNull(message = "用户ID不能为空")
private Long id;
/**
* 用户昵称
*/
@NotNull(message = "用户昵称不能为空")
private String nickName;
/**
* 用户头像
*/
@NotNull(message = "用户头像不能为空")
private String avatar;
}
vo:
AppUserVO:
java
package com.my.adminapi.appuser.domain.vo;
import lombok.Data;
import java.io.Serializable;
/**
* C 端用户 VO
*/
@Data
public class AppUserVO implements Serializable {
/**
* 用户id
*/
private Long id;
/**
* 用户昵称
*/
private String nickName;
/**
* 手机号
*/
private String phoneNumber;
/**
* 微信 唯一索引
*/
private String openId;
/**
* 头像
*/
private String avatar;
}
1.2.3 在 feign 下创建 FeignClient

AppUserFeignClient:
java
package com.my.adminapi.appuser.feign;
import com.my.adminapi.appuser.domain.dto.UserEditReqDTO;
import com.my.adminapi.appuser.domain.vo.AppUserVO;
import com.my.commondomain.domain.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient(contextId = "appUserFeignClient",value = "admin",path = "/app_user")
public interface AppUserFeignClient {
/**
* 根据openId查询用户信息
* @param openId 用户微信ID
* @return C端用户VO
*/
@GetMapping("/open_id_find")
R<AppUserVO> findByOpenId(@RequestParam String openId);
/**
* 根据微信注册用户
* @param openId 用户微信ID
* @return C端用户VO
*/
@GetMapping("/register/openid")
R<AppUserVO> registerByOpenId(@RequestParam String openId);
/**
* 根据手机号注册用户
* @param phoneNumber 手机号
* @return C端用户VO
*/
@GetMapping("/register/phone")
R<AppUserVO> registerByPhone(@RequestParam String phoneNumber);
/**
* 根据phoneNumber查询用户信息
* @param phoneNumber 用户手机号
* @return C端用户VO
*/
@GetMapping("/phone_find")
R<AppUserVO> findByPhone(@RequestParam String phoneNumber);
/**
* 编辑C端用户
* @param userEditReqDTO C端用户DTO
* @return void
*/
@PostMapping("/edit")
R<Void> edit(@RequestBody @Validated UserEditReqDTO userEditReqDTO);
/**
* 根据用户ID获取用户信息
* @param userId 用户ID
* @return C端用户VO
*/
@GetMapping("/id_find")
R<AppUserVO> findById(@RequestParam Long userId);
/**
* 根据用户ID列表获取用户列表信息
* @param userIds 用户ID列表
* @return C端用户VO列表
*/
@PostMapping("/list")
R<List<AppUserVO>> list(@RequestBody List<Long> userIds);
}
1.3 创建 portal 模块并实现
1.3.1 在 frameworkjava 下创建 portal 模块

1.3.2 创建 portal-api 和portal-service 包

1.3.3 在 portal-service 下创建启动类、包路径和 user 包

PortalServiceApplication:
java
package com.my.portalservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
@SpringBootApplication
@EnableFeignClients(basePackages = "com.my.**.feign")
public class PortalServiceApplication {
public static void main(String[] args) {
SpringApplication.run(PortalServiceApplication.class, args);
}
}
1.3.4 添加 maven 依赖
pom.xml:
java
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.my</groupId>
<artifactId>portal</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.my</groupId>
<artifactId>portal-service</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>com.my</groupId>
<artifactId>common-cache</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.my</groupId>
<artifactId>portal-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.my</groupId>
<artifactId>common-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.my</groupId>
<artifactId>common-redis</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.my</groupId>
<artifactId>common-security</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.my</groupId>
<artifactId>admin-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.my</groupId>
<artifactId>common-message</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
1.3.5 配置 bootstrap.yml

bootstrap.yml:
java
server:
port: 18083
spring:
application:
name: portal
profiles:
active: ${RUN_ENV}
cloud:
nacos:
discovery:
username: nacos
password: bite@123
namespace: frameworkjava-${RUN_ENV}
server-addr: ${NACOS_ADDR}
config:
username: nacos
password: bite@123
namespace: frameworkjava-${RUN_ENV}
server-addr: ${NACOS_ADDR}
file-extension: yaml
# 共享配置
shared-configs:
- data-id: share-common-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
refresh: true
- data-id: share-redis-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
refresh: true
- data-id: share-mysql-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
refresh: true
- data-id: share-caffeine-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
refresh: true
- data-id: share-map-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
refresh: true
- data-id: share-rabbitmq-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
refresh: true
- data-id: share-sms-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
refresh: true
logging:
pattern:
console: '[%thread] %-5level %logger{36} - %msg%n'
1.3.6 在 user 下创建 controller、service、domain包
因为 CRUD 操作都在 admin 模块下,所以没有 mapper 包

1.3.7 在 domain 下定义 dto、vo

dto:
登录基类LoginDTO:
java
package com.my.portalservice.user.domain.dto;
public class LoginDTO{
}
CodeLoginDTO:
java
package com.my.portalservice.user.domain.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 手机验证码登录
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class CodeLoginDTO extends LoginDTO{
/**
* 登录手机号
*/
@NotBlank(message = "手机号不能为空")
private String phoneNumber;
/**
* 验证码
*/
@NotBlank(message = "验证码不能为空")
private String code;
}
WechatLoginDTO:
java
package com.my.portalservice.user.domain.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 微信登录信息
*/
@Data
public class WechatLoginDTO extends LoginDTO{
/**
* 微信openId
*/
@NotBlank(message = "微信openId不能为空")
private String openId;
}
UserDTO:
java
package com.my.portalservice.user.domain.dto;
import com.my.commoncore.utils.BeanCopyUtil;
import com.my.commonsecurity.domain.dto.LoginUserDTO;
import com.my.portalservice.user.domain.vo.UserVO;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class UserDTO extends LoginUserDTO {
/**
* 用户头像
*/
private String avatar;
/**
* 对象转换
* @return
*/
public UserVO convertToVO() {
UserVO userVO = new UserVO();
BeanCopyUtil.copyProperties(this, userVO);
return userVO;
}
}
vo:
UserVO:
java
package com.my.portalservice.user.domain.vo;
import com.my.commonsecurity.domain.vo.LoginUserVO;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* C端用户VO
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class UserVO extends LoginUserVO {
/**
* 用户头像
*/
private String avatar;
}
1.3.8 在 service 下创建接口及其实现类

IUserService:
java
package com.my.portalservice.user.service;
import com.my.adminapi.appuser.domain.dto.UserEditReqDTO;
import com.my.commonsecurity.domain.dto.TokenDTO;
import com.my.portalservice.user.domain.dto.LoginDTO;
import com.my.portalservice.user.domain.dto.UserDTO;
import com.my.portalservice.user.domain.dto.WechatLoginDTO;
public interface IUserService {
/**
* 发送短信验证码
* @param phone 手机号
* @return 验证码
*/
void sendCode(String phone);
/**
* 登录逻辑
* @param loginDTO 用户登录DTO
* @return TokenDTO 加令牌
*/
TokenDTO login(LoginDTO loginDTO);
/**
* 修改用户信息
* @param userEditReqDTO C端用户编辑DTO
* @return void
*/
void edit(UserEditReqDTO userEditReqDTO);
/**
* 获取用户登录信息
* @return 用户信息VO
*/
UserDTO getLoginUser();
/**
* 退出登录
* @return void
*/
void logout();
}
UserServiceImpl:
java
package com.my.portalservice.user.service.Impl;
import com.my.adminapi.appuser.domain.dto.UserEditReqDTO;
import com.my.adminapi.appuser.domain.vo.AppUserVO;
import com.my.adminapi.appuser.feign.AppUserFeignClient;
import com.my.commoncore.utils.BeanCopyUtil;
import com.my.commoncore.utils.VerifyUtil;
import com.my.commondomain.domain.R;
import com.my.commondomain.domain.ResultCode;
import com.my.commondomain.exception.ServiceException;
import com.my.commonmessage.service.CaptchaService;
import com.my.commonsecurity.domain.dto.LoginUserDTO;
import com.my.commonsecurity.domain.dto.TokenDTO;
import com.my.commonsecurity.service.TokenService;
import com.my.commonsecurity.utils.JWTUtil;
import com.my.commonsecurity.utils.SecurityUtil;
import com.my.portalservice.user.domain.dto.CodeLoginDTO;
import com.my.portalservice.user.domain.dto.LoginDTO;
import com.my.portalservice.user.domain.dto.UserDTO;
import com.my.portalservice.user.domain.dto.WechatLoginDTO;
import com.my.portalservice.user.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private CaptchaService captchaService;
@Autowired
private AppUserFeignClient appUserFeignClient;
@Autowired
private TokenService tokenService;
/**
* 发送短信验证码
* @param phone 手机号
* @return 验证码
*/
@Override
public void sendCode(String phone) {
if(!VerifyUtil.checkPhone(phone)){
throw new ServiceException("手机号格式错误", ResultCode.INVALID_PARA.getCode());
}
captchaService.sendCode(phone);
}
/**
* 登录逻辑
* @param loginDTO 用户登录DTO
* @return TokenDTO 加令牌
*/
@Override
public TokenDTO login(LoginDTO loginDTO) {
// 1. 创建用户生命周期
LoginUserDTO loginUserDTO = new LoginUserDTO();
// 2. 区分登录方式进行,登录或注册
if(loginDTO instanceof WechatLoginDTO wechatLoginDTO) {
// 3. 处理微信登录的逻辑
// 将 loginUserDTO 传入
loginByWechat(wechatLoginDTO,loginUserDTO);
} else if(loginDTO instanceof CodeLoginDTO codeLoginDTO) {
// 4. 处理验证码登录的逻辑
loginByCode(codeLoginDTO,loginUserDTO);
}
// 5. 设置用户生命周期
loginUserDTO.setUserFrom("app");
// 6. 调用TokenService 设置生命周期
TokenDTO token = tokenService.createToken(loginUserDTO);
return token;
}
/**
* 修改用户信息
* @param userEditReqDTO C端用户编辑DTO
* @return void
*/
@Override
public void edit(UserEditReqDTO userEditReqDTO) {
R<Void> result = appUserFeignClient.edit(userEditReqDTO);
if(result == null || result.getCode() != ResultCode.SUCCESS.getCode()){
throw new ServiceException("修改用户失败");
}
}
/**
* 获取用户登录信息
* @return 用户信息VO
*/
@Override
public UserDTO getLoginUser() {
// 1. 获取当前登录的用户
LoginUserDTO loginUser = tokenService.getLoginUser();
if(loginUser == null){
throw new ServiceException("用户令牌有误", ResultCode.INVALID_PARA.getCode());
}
// 2. 远程查询用户信息
R<AppUserVO> userInfo = appUserFeignClient.findById(loginUser.getUserId());
if(userInfo == null || userInfo.getCode() != ResultCode.SUCCESS.getCode() || userInfo.getData() == null){
throw new ServiceException("查询用户失败", ResultCode.INVALID_PARA.getCode());
}
// 3 对象拼装,返回结果
UserDTO userDTO = new UserDTO();
BeanCopyUtil.copyProperties(userInfo.getData(),userDTO);
BeanCopyUtil.copyProperties(loginUser,userDTO);
return userDTO;
}
/**
* 退出登录
* @return void
*/
@Override
public void logout() {
// 1. 获取当前登录的用户
// LoginUserDTO loginUser = tokenService.getLoginUser();
// 2. 删除用户生命周期 -- 删除用户缓存记录
// tokenService.delLoginUser(loginUser.getToken());
// 1. 解析令牌
String token = SecurityUtil.getToken();
if(StringUtils.isBlank(token)) {
return;
}
String userId = JWTUtil.getUserId(token);
String userName = JWTUtil.getUserName(token);
log.info("{}退出了系统, 用户ID{}", userName, userId);
// 2. 删除用户生命周期 -- 删除用户缓存记录
tokenService.delLoginUser(token);
}
/**
* 验证码登录逻辑
* @param codeLoginDTO 验证码登录DTO
* @param loginUserDTO 用户生命周期
*/
private void loginByCode(CodeLoginDTO codeLoginDTO, LoginUserDTO loginUserDTO) {
AppUserVO appUserVO = null;
// 1. 查找是否存在数据
R<AppUserVO> result = appUserFeignClient.findByPhone(codeLoginDTO.getPhoneNumber());
// 2. 对结果进行校验
if(result == null || result.getCode() != ResultCode.SUCCESS.getCode() || result.getData() == null){
// 3. 用户不存在进行注册
appUserVO = register(codeLoginDTO);
} else {
// 4. 用户存在逻辑
appUserVO = result.getData();
}
// 5. 判断验证码是否正确
String code = captchaService.getCode(codeLoginDTO.getPhoneNumber());
if(code == null) {
throw new ServiceException("验证码无效",ResultCode.INVALID_PARA.getCode());
}
if(!code.equals(codeLoginDTO.getCode())) {
throw new ServiceException("验证码错误",ResultCode.INVALID_PARA.getCode());
}
// 6. 删除 验证码
captchaService.delCode(codeLoginDTO.getPhoneNumber());
// 7. 构建 用户生命周期
loginUserDTO.setUserId(appUserVO.getId());
loginUserDTO.setUserName(appUserVO.getNickName());
}
/**
* 微信登录逻辑
* @param wechatLoginDTO 微信登录DTO
* @param loginUserDTO 用户生命周期
*/
private void loginByWechat(WechatLoginDTO wechatLoginDTO, LoginUserDTO loginUserDTO) {
AppUserVO appUserVO = null;
// 1. 查找是否存在数据
R<AppUserVO> result = appUserFeignClient.findByOpenId(wechatLoginDTO.getOpenId());
// 2. 对结果进行校验
if(result == null || result.getCode() != ResultCode.SUCCESS.getCode() || result.getData() == null) {
// 3. 用户不存在进行注册
appUserVO = register(wechatLoginDTO);
} else{
// 4. 用户存在逻辑
appUserVO = result.getData();
}
// 5. 构建 用户生命周期
loginUserDTO.setUserName(appUserVO.getNickName());
loginUserDTO.setUserId(appUserVO.getId());
}
/**
* 根据入参来注册
* @param loginDTO 用户生命周期信息
* @return 用户VO
*/
private AppUserVO register(LoginDTO loginDTO) {
R<AppUserVO> result = null;
// 1. 判断注册类型
if(loginDTO instanceof WechatLoginDTO wechatLoginDTO) {
// 2. 微信注册的逻辑
result = appUserFeignClient.registerByOpenId(wechatLoginDTO.getOpenId());
if(result == null || result.getCode() != ResultCode.SUCCESS.getCode() || result.getData() == null) {
log.error("用户注册失败! {}", wechatLoginDTO.getOpenId());
throw new ServiceException("用户注册失败");
}
} else if(loginDTO instanceof CodeLoginDTO codeLoginDTO) {
// 3. 验证码登录逻辑
result = appUserFeignClient.registerByPhone(codeLoginDTO.getPhoneNumber());
if(result == null || result.getCode() != ResultCode.SUCCESS.getCode() || result.getData() == null) {
log.error("用户注册失败! {}", codeLoginDTO.getPhoneNumber());
throw new ServiceException("用户注册失败");
}
}
return result.getData();
}
}
1.3.9 在controller 下创建控制器

UserController:
java
package com.my.portalservice.user.controller;
import com.my.adminapi.appuser.domain.dto.UserEditReqDTO;
import com.my.commondomain.domain.R;
import com.my.commonsecurity.domain.vo.TokenVO;
import com.my.portalservice.user.domain.dto.CodeLoginDTO;
import com.my.portalservice.user.domain.dto.WechatLoginDTO;
import com.my.portalservice.user.domain.vo.UserVO;
import com.my.portalservice.user.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 门户程序用户入口
*/
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
/**
* 微信登录
* @param wechatLoginDTO 微信登录DTO
* @return token令牌
*/
@PostMapping("/login/wechat")
public R<TokenVO> login(@RequestBody @Validated WechatLoginDTO wechatLoginDTO){
return R.success(userService.login(wechatLoginDTO).convertTokenVO());
}
/**
* 验证码登录
* @param codeLoginDTO 验证码登录DTO
* @return token令牌
*/
@PostMapping("/login/code")
public R<TokenVO> login(@RequestBody @Validated CodeLoginDTO codeLoginDTO){
return R.success(userService.login(codeLoginDTO).convertTokenVO());
}
/**
* 发送短信验证码
* @param phone 手机号
* @return 验证码
*/
@GetMapping("/send_code")
public R<Void> sendCode(@RequestParam String phone) {
userService.sendCode(phone);
return R.success();
}
/**
* 修改用户信息
* @param userEditReqDTO C端用户编辑DTO
* @return void
*/
@PostMapping("/edit")
public R<Void> edit(@RequestBody @Validated UserEditReqDTO userEditReqDTO) {
userService.edit(userEditReqDTO);
return R.success();
}
/**
* 获取用户登录信息
* @return 用户信息VO
*/
@GetMapping("/login_info/get")
public R<UserVO> getLoginUser() {
return R.success(userService.getLoginUser().convertToVO());
}
/**
* 退出登录
* @return void
*/
@DeleteMapping("/logout")
public R<Void> logout() {
userService.logout();
return R.success();
}
}
1.4 在 admin-service 下提供 CRUD 等服务
1.4.1 在 user 包的 domain 下添加 C端用户的dto、vo、entity

dto:
AppUserDTO:
java
package com.my.adminservice.user.domain.dto;
import com.my.adminapi.appuser.domain.vo.AppUserVO;
import com.my.commoncore.utils.BeanCopyUtil;
import lombok.Data;
import java.io.Serializable;
@Data
public class AppUserDTO implements Serializable {
/**
* 用户id
*/
private Long id;
/**
* 用户昵称
*/
private String nickName;
/**
* 手机号
*/
private String phoneNumber;
/**
* 微信 唯一索引
*/
private String openId;
/**
* 头像
*/
private String avatar;
/**
* DTO 到 VO 转换
* @return AppUserVO
*/
public AppUserVO convertToAppUserVO() {
AppUserVO appUserVO = new AppUserVO();
BeanCopyUtil.copyProperties(this, appUserVO);
return appUserVO;
}
}
entity:
AppUser:
java
package com.my.adminservice.user.domain.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.my.commondomain.domain.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* C端用户对象 app_user
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName(value = "app_user")
public class AppUser extends BaseDO {
/**
* 用户昵称
*/
private String nickName;
/**
* 手机号
*/
private String phoneNumber;
/**
* 微信 唯一索引
*/
private String openId;
/**
* 头像
*/
private String avatar;
}
1.4.2 在 mapper 下创建对应数据表的接口

AppUserMapper:
java
package com.my.adminservice.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.my.adminapi.appuser.domain.dto.AppUserListReqDTO;
import com.my.adminservice.user.domain.entity.AppUser;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface AppUserMapper extends BaseMapper<AppUser> {
AppUser selectByOpenId(@Param("openId") String openId);
AppUser selectByPhone(@Param("phoneNumber") String phoneNumber);
Long selectCount(@Param("item") AppUserListReqDTO appUserListReqDTO);
List<AppUser> selectPage (@Param("item") AppUserListReqDTO appUserListReqDTO);
}
实现的 xml 文件
AppUserMapper.xml:
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.my.adminservice.user.mapper.AppUserMapper">
<select id="selectByOpenId" resultType="com.my.adminservice.user.domain.entity.AppUser">
select * from app_user where open_id = #{openId}
</select>
<select id="selectByPhone" resultType="com.my.adminservice.user.domain.entity.AppUser">
select * from app_user where phone_number = #{phoneNumber}
</select>
<select id="selectCount" resultType="java.lang.Long">
select count(1) from app_user
<where>
<if test="item.userId != null and item.userId != ''">
and id = #{item.userId}
</if>
<if test="item.phoneNumber != null and item.phoneNumber != ''">
and phone_number = #{item.phoneNumber}
</if>
<if test="item.nickName != null and item.nickName != ''">
and nick_name like concat('%', #{item.nickName}, '%')
</if>
<if test="item.openId != null and item.openId != ''">
and open_id = #{item.openId}
</if>
</where>
</select>
<select id="selectPage" resultType="com.my.adminservice.user.domain.entity.AppUser">
select * from app_user
<where>
<if test="item.userId != null and item.userId != ''">
and id = #{item.userId}
</if>
<if test="item.phoneNumber != null and item.phoneNumber != ''">
and phone_number = #{item.phoneNumber}
</if>
<if test="item.nickName != null and item.nickName != ''">
and nick_name like concat('%', #{item.nickName}, '%')
</if>
<if test="item.openId != null and item.openId != ''">
and open_id = #{item.openId}
</if>
</where>
order by id desc
limit #{item.offset},#{item.pageSize}
</select>
</mapper>
1.4.3 在 service 下创建接口及其实现类

IAppUserService:
java
package com.my.adminservice.user.service;
import com.my.adminapi.appuser.domain.dto.AppUserListReqDTO;
import com.my.adminapi.appuser.domain.dto.UserEditReqDTO;
import com.my.adminservice.user.domain.dto.AppUserDTO;
import com.my.commondomain.domain.dto.BasePageDTO;
import java.util.List;
public interface IAppUserService {
/**
* 根据openId查询用户信息
* @param openId 用户微信ID
* @return C端用户VO
*/
AppUserDTO findByOpenId(String openId);
/**
* 根据微信注册用户
* @param openId 用户微信ID
* @return C端用户VO
*/
AppUserDTO registerByOpenId(String openId);
/**
* 根据手机号注册用户
* @param phoneNumber 手机号
* @return C端用户VO
*/
AppUserDTO registerByPhone(String phoneNumber);
/**
* 根据phoneNumber查询用户信息
* @param phoneNumber 用户手机号
* @return C端用户VO
*/
AppUserDTO findByPhone(String phoneNumber);
/**
* 编辑C端用户
* @param userEditReqDTO C端用户DTO
* @return void
*/
void edit(UserEditReqDTO userEditReqDTO);
/**
* 根据用户ID获取用户信息
* @param userId 用户ID
* @return C端用户VO
*/
AppUserDTO findById(Long userId);
/**
* 根据用户ID列表获取用户列表信息
* @param userIds 用户ID列表
* @return C端用户VO列表
*/
List<AppUserDTO> getUserList(List<Long> userIds);
/**
* 查询C端用户
* @param appUserListReqDTO 查询C端用户DTO
* @return C端用户分页结果
*/
BasePageDTO<AppUserDTO> getUserList(AppUserListReqDTO appUserListReqDTO);
}
AppUserServiceImpl:
java
package com.my.adminservice.user.service.Impl;
import com.my.adminapi.appuser.domain.dto.AppUserListReqDTO;
import com.my.adminapi.appuser.domain.dto.UserEditReqDTO;
import com.my.adminservice.user.config.RabbitConfig;
import com.my.adminservice.user.domain.dto.AppUserDTO;
import com.my.adminservice.user.domain.entity.AppUser;
import com.my.adminservice.user.mapper.AppUserMapper;
import com.my.adminservice.user.service.IAppUserService;
import com.my.commoncore.utils.AESUtil;
import com.my.commoncore.utils.BeanCopyUtil;
import com.my.commoncore.utils.PageUtil;
import com.my.commoncore.utils.VerifyUtil;
import com.my.commondomain.domain.dto.BasePageDTO;
import com.my.commondomain.domain.ResultCode;
import com.my.commondomain.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Service
@RefreshScope
public class AppUserServiceImpl implements IAppUserService {
@Autowired
private AppUserMapper appUserMapper;
@Autowired
private RabbitTemplate rabbitTemplate;
@Value("${appuser.info.defaultAvatar}")
private String defaultAvatar;
/**
* 根据openId查询用户信息
* @param openId 用户微信ID
* @return C端用户VO
*/
@Override
public AppUserDTO findByOpenId(String openId) {
if(StringUtils.isBlank(openId)){
return null;
}
// 1 查询appUser实体类
AppUser appUser = appUserMapper.selectByOpenId(openId);
// 2 对查出来的结果进行判断
if(appUser == null){
return null;
}
AppUserDTO appUserDTO = new AppUserDTO();
BeanCopyUtil.copyProperties(appUser,appUserDTO);
// 3 处理手机号
appUserDTO.setPhoneNumber(AESUtil.decryptHex(appUser.getPhoneNumber()));
return appUserDTO;
}
/**
* 根据微信注册用户
* @param openId 用户微信ID
* @return C端用户VO
*/
@Override
public AppUserDTO registerByOpenId(String openId) {
if(StringUtils.isBlank(openId)){
throw new ServiceException("微信ID不能为空", ResultCode.INVALID_PARA.getCode());
}
// 1. 构建注册信息
AppUser appUser = new AppUser();
appUser.setOpenId(openId);
appUser.setNickName("普通用户" + (int) (Math.random() * 9000) + 1000);
appUser.setAvatar(defaultAvatar);
appUserMapper.insert(appUser);
// 2. 构建返回信息
AppUserDTO appUserDTO = new AppUserDTO();
BeanCopyUtil.copyProperties(appUser,appUserDTO);
return appUserDTO;
}
/**
* 根据手机号注册用户
* @param phoneNumber 手机号
* @return C端用户VO
*/
@Override
public AppUserDTO registerByPhone(String phoneNumber) {
if(!VerifyUtil.checkPhone(phoneNumber)){
throw new ServiceException("手机号不能为空", ResultCode.INVALID_PARA.getCode());
}
// 1. 构建注册信息
AppUser appUser = new AppUser();
appUser.setPhoneNumber(AESUtil.encryptHex(phoneNumber));
appUser.setNickName("普通用户" + (int) (Math.random() * 9000) + 1000);
appUser.setAvatar(defaultAvatar);
appUserMapper.insert(appUser);
// 2. 构建返回信息
AppUserDTO appUserDTO = new AppUserDTO();
BeanCopyUtil.copyProperties(appUser,appUserDTO);
appUserDTO.setPhoneNumber(phoneNumber);
return appUserDTO;
}
/**
* 根据phoneNumber查询用户信息
* @param phoneNumber 用户手机号
* @return C端用户VO
*/
@Override
public AppUserDTO findByPhone(String phoneNumber) {
if(!VerifyUtil.checkPhone(phoneNumber)){
throw new ServiceException("手机号不能为空", ResultCode.INVALID_PARA.getCode());
}
// 1. 查找数据库
AppUser appUser = appUserMapper.selectByPhone(AESUtil.encryptHex(phoneNumber));
if(appUser == null){
return null;
}
// 2. 构建返回信息
AppUserDTO appUserDTO = new AppUserDTO();
BeanCopyUtil.copyProperties(appUser,appUserDTO);
appUserDTO.setPhoneNumber(phoneNumber);
return appUserDTO;
}
/**
* 编辑C端用户
* @param userEditReqDTO C端用户DTO
* @return void
*/
@Override
public void edit(UserEditReqDTO userEditReqDTO) {
// 1. 查找数据库,看用户是否存在
AppUser appUser = appUserMapper.selectById(userEditReqDTO.getId());
if(appUser == null){
throw new ServiceException("用户不存在", ResultCode.INVALID_PARA.getCode());
}
// 2 查到用户,进行编辑操作
appUser.setNickName(userEditReqDTO.getNickName());
appUser.setAvatar(defaultAvatar);
appUserMapper.updateById(appUser);
// 3 发送广播消息
AppUserDTO appUserDTO = new AppUserDTO();
BeanCopyUtil.copyProperties(appUser,appUserDTO);
try{
rabbitTemplate.convertAndSend(RabbitConfig.EXCHANGE_NAME,"",appUserDTO);
} catch (Exception exception) {
log.error("编辑用户发送消息失败", exception);
}
}
/**
* 根据用户ID获取用户信息
* @param userId 用户ID
* @return C端用户VO
*/
@Override
public AppUserDTO findById(Long userId) {
if(userId == null){
return null;
}
// 1. 查询信息
AppUser appUser = appUserMapper.selectById(userId);
if(appUser == null){
return null;
}
// 2. 构建返回信息
AppUserDTO appUserDTO = new AppUserDTO();
BeanCopyUtil.copyProperties(appUser,appUserDTO);
appUserDTO.setPhoneNumber(AESUtil.decryptHex(appUser.getPhoneNumber()));
return appUserDTO;
}
/**
* 根据用户ID列表获取用户列表信息
* @param userIds 用户ID列表
* @return C端用户VO列表
*/
@Override
public List<AppUserDTO> getUserList(List<Long> userIds) {
// 1 对入参进行判空
if(CollectionUtils.isEmpty(userIds)){
return new ArrayList<>();
}
// 2 查询appUser列表
List<AppUser> appUserList = appUserMapper.selectBatchIds(userIds);
// 3. 对象转换
return appUserList.stream()
.map(entity -> {
AppUserDTO appUserDTO = new AppUserDTO();
BeanCopyUtil.copyProperties(entity,appUserDTO);
appUserDTO.setPhoneNumber(AESUtil.decryptHex(entity.getPhoneNumber()));
return appUserDTO;
}).toList();
}
/**
* 查询C端用户
* @param appUserListReqDTO 查询C端用户DTO
* @return C端用户分页结果
*/
@Override
public BasePageDTO<AppUserDTO> getUserList(AppUserListReqDTO appUserListReqDTO) {
// 1. 将手机号加密
appUserListReqDTO.setPhoneNumber(AESUtil.encryptHex(appUserListReqDTO.getPhoneNumber()));
// 2. 创建返回结果
BasePageDTO<AppUserDTO> result = new BasePageDTO();
// 3. 查询总数
Long totals = appUserMapper.selectCount(appUserListReqDTO);
if(totals == 0) {
result.setTotals(0);
result.setTotalPages(0);
result.setList(new ArrayList<>());
return result;
}
// 4. 填充总数
result.setTotals(totals.intValue());
// 5. 分页查询
List<AppUser> entityList = appUserMapper.selectPage(appUserListReqDTO);
// 6. 填充页数
result.setTotalPages(
PageUtil.getTotalPages(totals,appUserListReqDTO.getPageSize()));
// 7. 超页
if (CollectionUtils.isEmpty(entityList)) {
result.setList(new ArrayList<>());
return result;
}
// 8. 填充用户结果
result.setList(
entityList.stream()
.map(entity -> {
AppUserDTO dto = new AppUserDTO();
BeanCopyUtil.copyProperties(entity,dto);
dto.setPhoneNumber(AESUtil.decryptHex(entity.getPhoneNumber()));
return dto;
}).toList()
);
// 9. 返回结果
return result;
}
}
1.4.4 在 controller 下实现远程调用的控制器

AppUserController:
java
package com.my.adminservice.user.controller;
import com.my.adminapi.appuser.domain.dto.AppUserListReqDTO;
import com.my.adminapi.appuser.domain.dto.UserEditReqDTO;
import com.my.adminapi.appuser.domain.vo.AppUserVO;
import com.my.adminapi.appuser.feign.AppUserFeignClient;
import com.my.adminservice.user.domain.dto.AppUserDTO;
import com.my.adminservice.user.service.IAppUserService;
import com.my.commoncore.utils.BeanCopyUtil;
import com.my.commondomain.domain.dto.BasePageDTO;
import com.my.commondomain.domain.vo.BasePageVO;
import com.my.commondomain.domain.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Objects;
/**
* C端用户相关接口实现
*/
@RestController
@RequestMapping("/app_user")
public class AppUserController implements AppUserFeignClient {
@Autowired
private IAppUserService appUserService;
/**
* 根据openId查询用户信息
* @param openId 用户微信ID
* @return C端用户VO
*/
@Override
public R<AppUserVO> findByOpenId(String openId) {
AppUserDTO result = appUserService.findByOpenId(openId);
if(result == null){
return R.success();
}
return R.success(result.convertToAppUserVO());
}
/**
* 根据微信注册用户
* @param openId 用户微信ID
* @return C端用户VO
*/
@Override
public R<AppUserVO> registerByOpenId(String openId) {
return R.success(appUserService.registerByOpenId(openId).convertToAppUserVO());
}
/**
* 根据手机号注册用户
* @param phoneNumber 手机号
* @return C端用户VO
*/
@Override
public R<AppUserVO> registerByPhone(String phoneNumber) {
return R.success(appUserService.registerByPhone(phoneNumber).convertToAppUserVO());
}
/**
* 根据phoneNumber查询用户信息
* @param phoneNumber 用户手机号
* @return C端用户VO
*/
@Override
public R<AppUserVO> findByPhone(String phoneNumber) {
AppUserDTO result = appUserService.findByPhone(phoneNumber);
if(result == null){
return R.success();
}
return R.success(result.convertToAppUserVO());
}
/**
* 编辑C端用户
* @param userEditReqDTO C端用户DTO
* @return void
*/
@Override
public R<Void> edit(UserEditReqDTO userEditReqDTO) {
appUserService.edit(userEditReqDTO);
return R.success();
}
/**
* 根据用户ID获取用户信息
* @param userId 用户ID
* @return C端用户VO
*/
@Override
public R<AppUserVO> findById(Long userId) {
AppUserDTO result = appUserService.findById(userId);
if(result == null){
return R.success();
}
return R.success(result.convertToAppUserVO());
}
/**
* 根据用户ID列表获取用户列表信息
* @param userIds 用户ID列表
* @return C端用户VO列表
*/
@Override
public R<List<AppUserVO>> list(List<Long> userIds) {
List<AppUserDTO> result = appUserService.getUserList(userIds);
return R.success(
result.stream()
.filter(Objects::nonNull)
.map(AppUserDTO::convertToAppUserVO)
.toList()
);
}
/**
* 查询C端用户
* @param appUserListReqDTO 查询C端用户DTO
* @return C端用户分页结果
*/
@PostMapping("/list/search")
public R<BasePageVO<AppUserVO>> list(@RequestBody AppUserListReqDTO appUserListReqDTO) {
BasePageDTO<AppUserDTO> pageDTO = appUserService.getUserList(appUserListReqDTO);
BasePageVO<AppUserVO> result = new BasePageVO<>();
result.setTotals(pageDTO.getTotals());
result.setTotalPages(pageDTO.getTotalPages());
result.setList(
pageDTO.getList().stream()
.filter(Objects::nonNull)
.map(dto -> {
AppUserVO vo = new AppUserVO();
BeanCopyUtil.copyProperties(dto, vo);
return vo;
}).toList()
);
return R.success(result);
}
}
二、添加 nacos 配置


admin-dev.yaml:
yaml
# 用戶
appuser:
info:
defaultAvatar: 自己设置的默认头像链接


share-common-dev.yaml:
yaml
# feign 配置
feign:
okhttp:
enabled: true
httpclient:
enabled: false
client:
config:
default:
connectTimeout: 10000
readTimeout: 10000
compression:
request:
enabled: true
response:
enabled: true
三、测试
发送验证码:


登录并注册:


获取用户登录信息:


END
因为字太多了,下一张补充短信服务