目录
9.1.1 用户身份登录统一拦截
- 微服务请求鉴权
9.1.1.1 创建电商服务模块父模块
- 创建 sca-commerce-service 是 sca-commerce 的子项目
- 创建 sca-commerce-service-config 是 sca-commerce-service 的子项目
微服务的架构图
sca-commerce-service
pom.xml
<?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>
<artifactId>sca-commerce</artifactId>
<groupId>com.edcode.commerce</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sca-commerce-service</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>sca-commerce-service-config</module>
</modules>
<packaging>pom</packaging> <!-- 父模块不能 jar 只能是 pom -->
<!-- 模块名及描述信息 -->
<name>sca-commerce-service</name>
<description>电商服务模块父模块</description>
</project>
sca-commerce-service-config
pom.xml
<?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>
<artifactId>sca-commerce-service</artifactId>
<groupId>com.edcode.commerce</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sca-commerce-service-config</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 模块名及描述信息 -->
<name>sca-commerce-service-config</name>
<description>服务通用配置</description>
<dependencies>
<!-- 引入 mvc-config, 变成 web 应用 -->
<dependency>
<groupId>com.edcode.commerce</groupId>
<artifactId>sca-commerce-mvc-config</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
9.1.1.2 利用 ThreadLocal 做用户身份统一登录拦截
sca-commerce-service-config
使用 ThreadLocal 去单独存储每一个线程携带的 LoginUserInfo 信息
package com.edcode.commerce.filter;
import com.edcode.commerce.vo.LoginUserInfo;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description
*
* 使用 ThreadLocal 去单独存储每一个线程携带的 LoginUserInfo 信息
* 要及时的清理我们保存到 ThreadLocal 中的用户信息:
* 1. 保证没有资源泄露
* 2. 保证线程在重用时, 不会出现数据混乱
*
*/
public class AccessContext {
/**
* 用户信息保存在 ThreadLocal 里面
*/
private static final ThreadLocal<LoginUserInfo> LOGIN_USER_INFO = new ThreadLocal<>();
/**
* 从 ThreadLocal 获取用户信息
*/
public static LoginUserInfo getLoginUserInfo() {
return LOGIN_USER_INFO.get();
}
/**
* 从拦截器或者 jwt 信息, set 到 ThreadLocal
*/
public static void setLoginUserInfo(LoginUserInfo loginUserInfo) {
LOGIN_USER_INFO.set(loginUserInfo);
}
/**
* 清除 ThreadLocal 里面的当前线程用户信息 (必需要有,不然会 OOM )
*/
public static void clearLoginUserInfo() {
LOGIN_USER_INFO.remove();
}
}
ThreadLocal 在面试经常会给问到,所以此处可以当做一个实践回答,ThreadLocal 在项目做了什么和怎么用。
用户身份统一登录拦截
package com.edcode.commerce.filter;
import com.edcode.commerce.constant.CommonConstant;
import com.edcode.commerce.util.TokenParseUtil;
import com.edcode.commerce.vo.LoginUserInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 用户身份统一登录拦截
*/
@SuppressWarnings("all")
@Slf4j
@Component
public class LoginUserInfoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 先尝试从 http header 里面拿到 token
String token = request.getHeader(CommonConstant.JWT_USER_INFO_KEY);
LoginUserInfo loginUserInfo = null;
try {
loginUserInfo = TokenParseUtil.parseUserInfoFromToken(token);
} catch (Exception ex) {
log.error("解析登录用户信息错误: [{}]", ex.getMessage(), ex);
}
// 如果程序走到这里, 说明 header 中没有 token 信息 (这个判断基本不会跳进来,Gateway已经拦截过了)
if (null == loginUserInfo) {
throw new RuntimeException("无法分析当前登录用户");
}
log.info("设置登录用户信息: [{}]", request.getRequestURI());
// 设置当前请求上下文, 把用户信息填充进去
AccessContext.setLoginUserInfo(loginUserInfo);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 【postHandle 请求执行之后,返回之前】 不需要返回之前修改
}
/**
* 在请求完全结束后调用, 常用于清理资源等工作
* */
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 判断 ThreadLocal 是否有用户信息
if (null != AccessContext.getLoginUserInfo()) {
// 清除 ThreadLocal 里面用户信息
AccessContext.clearLoginUserInfo();
}
}
}
Web Mvc 配置
package com.edcode.commerce.conf;
import com.edcode.commerce.filter.LoginUserInfoInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description Web Mvc 配置
*/
@Configuration
public class ScaWebMvcConfig extends WebMvcConfigurationSupport {
/**
* 添加拦截器配置
*/
@Override
protected void addInterceptors(InterceptorRegistry registry) {
// 添加【用户身份统一登录拦截】
registry.addInterceptor(new LoginUserInfoInterceptor()).addPathPatterns("/**").order(0);
}
}
9.2.1 集成 Swagger2 实现代码即文档
9.2.1.1 集成 Swagger2 的步骤与原理
- 如何集成 Swagger2
- 在 pom.xml 文件中添加 Swagger2 所需要的依赖配置
- 配置 Swagger2,指定扫描包与描述展示信息
- Web MVC 配置:让 MVC 加载 Swagger 的静态资源
9.2.1.2 Maven 依赖
访问 https://mvnrepository.com/ 查找最新的 Swagger2 依赖
pom.xml
<dependencies>
<!-- swagger 用于定义 API 文档 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>
<!-- 美化 swagger -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
</dependencies>
9.2.1.3 编写 Swagger 配置类
package com.edcode.commerce.conf;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description Swagger 配置类
*
* ip:port/swagger-ui.html
*
* ip:port/doc.html
*
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
/**
* Swagger 实例 Bean 是 Docket, 所以通过配置 Docket 实例来配置 Swagger
*/
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
// 展示在 Swagger 页面上的自定义工程描述信息
.apiInfo(apiInfo())
// 选择展示哪些接口
.select()
// 只有 com.edcode.ecommerce 包内的才去展示
.apis(RequestHandlerSelectors.basePackage("com.edcode.commerce"))
.paths(PathSelectors.any())
.build();
}
/**
* Swagger 的描述信息
*/
public ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("sca-commerce-service")
.description("sca-commerce-springcloud-service")
.contact(new Contact(
"eddie", "blog.eddilee.cn", "xxx@qq.com"
))
.version("1.0")
.build();
}
}
9.3.1.1 配置 MVC 加载 Swagger 的静态资源
package com.edcode.commerce.conf;
import com.edcode.commerce.filter.LoginUserInfoInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description Web Mvc 配置
*/
@Configuration
public class ScaWebMvcConfig extends WebMvcConfigurationSupport {
/**
* 添加拦截器配置
*/
@Override
protected void addInterceptors(InterceptorRegistry registry) {
// 添加【用户身份统一登录拦截】
registry.addInterceptor(new LoginUserInfoInterceptor()).addPathPatterns("/**").order(0);
}
/**
* 让 MVC 加载 Swagger 的静态资源
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").
addResourceLocations("classpath:/static/");
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("doc.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
super.addResourceHandlers(registry);
}
}
9.3.1.2 校验是否是白名单 swagger2 接口
为 swagger2 接口,添加到拦截器里面,如果是 swagger2 接口的请求就不需要带有身份验证信息
package com.edcode.commerce.filter;
import com.edcode.commerce.constant.CommonConstant;
import com.edcode.commerce.util.TokenParseUtil;
import com.edcode.commerce.vo.LoginUserInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 用户身份统一登录拦截
*/
@SuppressWarnings("all")
@Slf4j
@Component
public class LoginUserInfoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 部分请求不需要带有身份信息, 即白名单
if (checkWhiteListUrl(request.getRequestURI())) {
return true;
}
// 先尝试从 http header 里面拿到 token
String token = request.getHeader(CommonConstant.JWT_USER_INFO_KEY);
LoginUserInfo loginUserInfo = null;
try {
loginUserInfo = TokenParseUtil.parseUserInfoFromToken(token);
} catch (Exception ex) {
log.error("解析登录用户信息错误: [{}]", ex.getMessage(), ex);
}
// 如果程序走到这里, 说明 header 中没有 token 信息 (这个判断基本不会跳进来,Gateway已经拦截过了)
if (null == loginUserInfo) {
throw new RuntimeException("无法分析当前登录用户");
}
log.info("设置登录用户信息: [{}]", request.getRequestURI());
// 设置当前请求上下文, 把用户信息填充进去
AccessContext.setLoginUserInfo(loginUserInfo);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 【postHandle 请求执行之后,返回之前】 不需要返回之前修改
}
/**
* 在请求完全结束后调用, 常用于清理资源等工作
* */
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 判断 ThreadLocal 是否有用户信息
if (null != AccessContext.getLoginUserInfo()) {
// 清除 ThreadLocal 里面用户信息
AccessContext.clearLoginUserInfo();
}
}
/**
* 校验是否是白名单接口
* swagger2 接口
* */
private boolean checkWhiteListUrl(String url) {
return StringUtils.containsAny(
url,
"springfox", "swagger", "v2",
"webjars", "doc.html"
);
}
}
9.3.1.3 集成 Swagger2 的步骤与原理
- Swagger2 的实现原理
- 项目启动,Spring 初始化上下文,加载 Swagger 相关的 Bean,并由此扫描系统中需要生成的 API 文档注解获取注释信息
9.4.1 用户账户微服务功能设计
- 用户账户微服务功能逻辑架构
- Tips:微服务功能设计看需求、看业务设定、看个人喜好
9.4.1.1 创建用户账户微服务模块
架构图
sca-commerce-account-service
bootstrap.yml
server:
port: 8003
servlet:
context-path: /scacommerce-account-service
spring:
application:
name: sca-commerce-account-service
cloud:
nacos:
# 服务注册发现
discovery:
enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
#server-addr: ${NACOS_ADDR:127.0.0.1}:8848
server-addr: ${NACOS_ADDR_1:127.0.0.1}:8848,${NACOS_ADDR_2:127.0.0.1}:8849,${NACOS_ADDR_3:127.0.0.1}:8850 # Nacos 服务器地址
namespace: ${NAMESPACE_ID:1adcfdd8-5763-4768-9a15-9c7157988950}
metadata:
management:
context-path: ${server.servlet.context-path}/actuator
kafka:
bootstrap-servers: ${KAFKA_SERVER:127.0.0.1}:${KAFKA_PORT:9092}
producer:
retries: 3
consumer:
auto-offset-reset: latest
zipkin:
sender:
type: ${ZIPKIN_KAFKA_SENDER:web} # 默认是 web
base-url: http://${ZIPKIN_URL:localhost}:${ZIPKIN_PORT:9411}/
sleuth:
sampler:
# RateLimitingSampler 抽样策略,设置了限速采样,spring.sleuth.sampler.probability 属性值无效
rate: 100 # 每秒间隔接受的 trace 量
# Probability 抽样策略
probability: 1.0 # 采样比例,1.0 表示 100%, 默认:0.1
jpa:
show-sql: true
hibernate:
ddl-auto: none
properties:
hibernate.show_sql: true
hibernate.format_sql: true
open-in-view: false
datasource:
# 数据源
url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:sca_commerce}?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
username: ${MYSQL_USERNAME:root}
password: ${MYSQL_PASSWORD:123456}
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
# 连接池
hikari:
maximum-pool-size: 8
minimum-idle: 4
idle-timeout: 30000
connection-timeout: 30000
max-lifetime: 45000
auto-commit: true
pool-name: ImoocEcommerceHikariCP
# 暴露端点
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
启动类
package com.edcode.commerce;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 用户账户微服务启动入口
*
* localhost:8003/scacommerce-account-service/doc.html
* localhost:8003/scacommerce-account-service/swagger-ui.html
*/
@EnableJpaAuditing // 审计功能
@SpringBootApplication
@EnableDiscoveryClient
public class AccountApplication {
public static void main(String[] args) {
SpringApplication.run(AccountApplication.class, args);
}
}
9.5.1 数据表及ORM过程 - 地址数据表
9.5.1.1 地址数据表语句
-- 创建 t_scacommerce_address 数据表
CREATE TABLE IF NOT EXISTS `sca_commerce`.`t_scacommerce_address` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`user_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '用户 id',
`username` varchar(64) NOT NULL DEFAULT '' COMMENT '用户名',
`phone` varchar(64) NOT NULL DEFAULT '' COMMENT '电话号码',
`province` varchar(64) NOT NULL DEFAULT '' COMMENT '省',
`city` varchar(64) NOT NULL DEFAULT '' COMMENT '市',
`address_detail` varchar(256) NOT NULL DEFAULT '' COMMENT '详细地址',
`create_time` datetime NOT NULL DEFAULT '0000-01-01 00:00:00' COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT '0000-01-01 00:00:00' COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='用户地址表';
使用 IDEA EasyCode 插件生成实体类
在 【Spring Cloud Alibaba 温故而知新】(三)授权、鉴权中心微服务 章节有 EasyCode 使用说明与 JPA 模板提供
9.5.1.2 地址数据表 - 逆向生成的实体类
package com.edcode.commerce.entity;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Entity;
import javax.persistence.GenerationType;
import org.hibernate.annotations.GenericGenerator;
import java.io.Serializable;
import javax.persistence.EntityListeners;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
/**
* @description 用户地址表(t_scacommerce_address)表实体类
* @author eddie.lee
* @date 2021-11-04 12:44:32
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "t_scacommerce_address")
@EntityListeners(AuditingEntityListener.class) // 作用:自动更新时间, 需要配合 @EnableJpaAuditing 使用
public class ScaCommerceAddress implements Serializable {
/**
* 自增主键
*/
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 用户 id
*/
@Column(name = "user_id", nullable = false)
private Long userId;
/**
* 用户名
*/
@Column(name = "username", nullable = false)
private String username;
/**
* 电话号码
*/
@Column(name = "phone", nullable = false)
private String phone;
/**
* 省
*/
@Column(name = "province", nullable = false)
private String province;
/**
* 市
*/
@Column(name = "city", nullable = false)
private String city;
/**
* 详细地址
*/
@Column(name = "address_detail", nullable = false)
private String addressDetail;
/**
* 创建时间
*/
@Column(name = "create_time", nullable = false)
private Date createTime;
/**
* 更新时间
*/
@Column(name = "update_time", nullable = false)
private Date updateTime;
}
实体类出现红色波浪线,警告需要加数据源,如果强迫症的小伙伴可以自行添加
选择数据源
9.5.1.3 地址 - DAO
package com.edcode.commerce.dao;
import com.edcode.commerce.entity.ScaCommerceAddress;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description ScaCommerceAddress Dao 接口定义
*/
public interface ScaCommerceAddressDao extends JpaRepository<ScaCommerceAddress, Long> {
/**
* 根据 用户 id 查询地址信息
*/
List<ScaCommerceAddress> findAllByUserId(Long userId);
}
9.5.1.4 创建一个对外的 sdk 子工程
sca-commerce-service-sdk
架构图
pom.xml
<?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>
<artifactId>sca-commerce-service</artifactId>
<groupId>com.edcode.commerce</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sca-commerce-service-sdk</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 模块名及描述信息 -->
<name>sca-commerce-service-sdk</name>
<description>服务模块 SDK</description>
</project>
用户地址信息
package com.edcode.commerce.account;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.List;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 返回给 - 用户地址信息
*/
@ApiModel(description = "用户地址信息")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AddressInfo {
@ApiModelProperty(value = "地址所属用户 id")
private Long userId;
@ApiModelProperty(value = "地址详细信息")
private List<AddressItem> addressItems;
/**
* 单个的地址信息
*/
@ApiModel(description = "用户的单个地址信息")
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class AddressItem {
@ApiModelProperty(value = "地址表主键 id")
private Long id;
@ApiModelProperty(value = "用户姓名")
private String username;
@ApiModelProperty(value = "电话")
private String phone;
@ApiModelProperty(value = "省")
private String province;
@ApiModelProperty(value = "市")
private String city;
@ApiModelProperty(value = "详细的地址")
private String addressDetail;
@ApiModelProperty(value = "创建时间")
private Date createTime;
@ApiModelProperty(value = "更新时间")
private Date updateTime;
public AddressItem(Long id) {
this.id = id;
}
/**
* 将 AddressItem 转换成 UserAddress
*/
public UserAddress toUserAddress() {
UserAddress userAddress = new UserAddress();
userAddress.setUsername(this.username);
userAddress.setPhone(this.phone);
userAddress.setProvince(this.province);
userAddress.setCity(this.city);
userAddress.setAddressDetail(this.addressDetail);
return userAddress;
}
}
}
UserAddress
package com.edcode.commerce.account;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 用户地址信息
*/
@ApiModel(description = "用户地址信息")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserAddress {
@ApiModelProperty(value = "用户姓名")
private String username;
@ApiModelProperty(value = "电话")
private String phone;
@ApiModelProperty(value = "省")
private String province;
@ApiModelProperty(value = "市")
private String city;
@ApiModelProperty(value = "详细的地址")
private String addressDetail;
}
sca-commerce-account-service 添加 sdk 的 Maven依赖
<dependency>
<groupId>com.edcode.commerce</groupId>
<artifactId>sca-commerce-service-sdk</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
9.6.1 数据表及ORM过程 - 用户账户余额表
9.6.1.1 用户账户余额表语句
-- 创建 t_scacommerce_balance 数据表
CREATE TABLE IF NOT EXISTS `sca_commerce`.`t_scacommerce_balance` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`user_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '用户 id',
`balance` bigint(20) NOT NULL DEFAULT 0 COMMENT '账户余额',
`create_time` datetime NOT NULL DEFAULT '0000-01-01 00:00:00' COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT '0000-01-01 00:00:00' COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `user_id_key` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='用户账户余额表';
9.6.1.2 用户账户余额表 - 逆向生成实体类
package com.edcode.commerce.entity;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Entity;
import javax.persistence.GenerationType;
import org.hibernate.annotations.GenericGenerator;
import java.io.Serializable;
import javax.persistence.EntityListeners;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
/**
* @description 用户账户余额表(t_scacommerce_balance)表实体类
* @author eddie.lee
* @date 2021-11-04 12:44:59
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "t_scacommerce_balance")
@EntityListeners(AuditingEntityListener.class) // 作用:自动更新时间, 需要配合 @EnableJpaAuditing 使用
public class ScaCommerceBalance implements Serializable {
/**
* 自增主键
*/
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 用户 id
*/
@Column(name = "user_id", nullable = false)
private Long userId;
/**
* 账户余额
*/
@Column(name = "balance", nullable = false)
private Long balance;
/**
* 创建时间
*/
@Column(name = "create_time", nullable = false)
private Date createTime;
/**
* 更新时间
*/
@Column(name = "update_time", nullable = false)
private Date updateTime;
}
9.6.1.3 用户账户余额 - DAO
package com.edcode.commerce.dao;
import com.edcode.commerce.entity.ScaCommerceBalance;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description ScaCommerceBalance Dao 接口定义
*/
public interface ScaCommerceBalanceDao extends JpaRepository<ScaCommerceBalance, Long> {
/**
* 根据 userId 查询 ScaCommerceBalance 对象
*/
ScaCommerceBalance findByUserId(Long userId);
}
9.6.1.4 sdk 创建交互的账户余额信息实体
package com.edcode.commerce.account;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 用户账户余额信息
*/
@ApiModel(description = "用户账户余额信息")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BalanceInfo {
@ApiModelProperty(value = "用户主键 id")
private Long userId;
@ApiModelProperty(value = "用户账户余额")
private Long balance;
}
9.7.1 用户地址与余额服务接口定义
9.7.1.1 主键 ids
为返回的数据 id 做一个实体类,在 sca-commerce-service-sdk
package com.edcode.commerce.common;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 主键 ids
*/
@ApiModel(description = "通用 id 对象")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TableId {
@ApiModelProperty(value = "数据表记录主键")
private List<Id> ids;
@ApiModel(description = "数据表记录主键对象")
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Id {
@ApiModelProperty(value = "数据表记录主键")
private Long id;
}
}
9.7.1.2 用户地址相关服务接口定义
package com.edcode.commerce.service;
import com.edcode.commerce.account.AddressInfo;
import com.edcode.commerce.common.TableId;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 用户地址相关服务接口定义
*/
public interface IAddressService {
/**
* 创建用户地址信息
*/
TableId createAddressInfo(AddressInfo addressInfo);
/**
* 获取当前登录的用户地址信息
*/
AddressInfo getCurrentAddressInfo();
/**
* 通过 id 获取用户地址信息, id 是 ScaCommerceAddress 表的主键
*/
AddressInfo getAddressInfoById(Long id);
/**
* 通过 TableId 获取用户地址信息
*/
AddressInfo getAddressInfoByTableId(TableId tableId);
}
9.7.1.3 用于余额相关的服务接口定义
package com.edcode.commerce.service;
import com.edcode.commerce.account.BalanceInfo;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 用于余额相关的服务接口定义
*/
public interface IBalanceService {
/**
* 获取当前用户余额信息
*/
BalanceInfo getCurrentUserBalanceInfo();
/**
* >扣减用户余额
*
* @param balanceInfo
* 代表想要扣减的余额
*/
BalanceInfo deductBalance(BalanceInfo balanceInfo);
}
9.8.1 用户地址相关服务接口实现
- 获取用户信息,不要相信前端传过来的,使用 AccessContext 获取
- 在编码时候,使用 stream().map 可以更方便的转换映射,不需要 for 循环的写法去做
package com.edcode.commerce.service.impl;
import com.alibaba.fastjson.JSON;
import com.edcode.commerce.account.AddressInfo;
import com.edcode.commerce.common.TableId;
import com.edcode.commerce.dao.ScaCommerceAddressDao;
import com.edcode.commerce.entity.ScaCommerceAddress;
import com.edcode.commerce.filter.AccessContext;
import com.edcode.commerce.service.IAddressService;
import com.edcode.commerce.vo.LoginUserInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 用户地址相关服务接口实现
*/
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class AddressServiceImpl implements IAddressService {
private final ScaCommerceAddressDao addressDao;
/**
* 存储多个地址信息
*/
@Override
public TableId createAddressInfo(AddressInfo addressInfo) {
// 不能直接从参数中获取用户的 id 信息
LoginUserInfo loginUserInfo = AccessContext.getLoginUserInfo();
// jdk7写法:将传递的参数转换成实体对象
// List<AddressInfo.AddressItem> addressItems = addressInfo.getAddressItems();
// List<ScaCommerceAddress> scaCommerceAddresses = new ArrayList<>();
//
// for (AddressInfo.AddressItem a : addressItems) {
// scaCommerceAddresses.add(ScaCommerceAddress.to(loginUserInfo.getId(), a));
// }
// jdk8写法(将对象转换为其他对象):将传递的参数转换成实体对象
List<ScaCommerceAddress> scaCommerceAddresses = addressInfo.getAddressItems().stream()
.map(a -> ScaCommerceAddress.to(loginUserInfo.getId(), a))
.collect(Collectors.toList());
// 保存到数据表并把返回记录的 id 给调用方
List<ScaCommerceAddress> savedRecords = addressDao.saveAll(scaCommerceAddresses);
List<Long> ids = savedRecords.stream().map(ScaCommerceAddress::getId).collect(Collectors.toList());
log.info("创建地址信息: [{}], [{}]", loginUserInfo.getId(), JSON.toJSONString(ids));
return new TableId(
// TODO TableId.Id::new
ids.stream().map(TableId.Id::new).collect(Collectors.toList())
);
}
@Override
public AddressInfo getCurrentAddressInfo() {
LoginUserInfo loginUserInfo = AccessContext.getLoginUserInfo();
// 根据 userId 查询到用户的地址信息, 再实现转换
List<ScaCommerceAddress> scaCommerceAddresses = addressDao.findAllByUserId(
loginUserInfo.getId()
);
List<AddressInfo.AddressItem> addressItems = scaCommerceAddresses.stream()
// .map(a -> a.toAddressItem())
.map(ScaCommerceAddress::toAddressItem)
.collect(Collectors.toList());
return new AddressInfo(loginUserInfo.getId(), addressItems);
}
@Override
public AddressInfo getAddressInfoById(Long id) {
ScaCommerceAddress scaCommerceAddress = addressDao.findById(id).orElse(null);
if (null == scaCommerceAddress) {
throw new RuntimeException("地址不存在");
}
return new AddressInfo(
scaCommerceAddress.getUserId(),
Collections.singletonList(scaCommerceAddress.toAddressItem())
);
}
@Override
public AddressInfo getAddressInfoByTableId(TableId tableId) {
List<Long> ids = tableId.getIds().stream()
// .map(e -> e.getId()).collect(Collectors.toList());
.map(TableId.Id::getId).collect(Collectors.toList());
log.info("按表id获取地址信息: [{}]", JSON.toJSONString(ids));
List<ScaCommerceAddress> scaCommerceAddresses = addressDao.findAllById(ids);
if (CollectionUtils.isEmpty(scaCommerceAddresses)) {
return new AddressInfo(-1L, Collections.emptyList());
}
List<AddressInfo.AddressItem> addressItems = scaCommerceAddresses.stream()
// .map(s -> s.toAddressItem())
.map(ScaCommerceAddress::toAddressItem)
.collect(Collectors.toList());
return new AddressInfo(
scaCommerceAddresses.get(0).getUserId(), addressItems
);
}
}
9.9.1 用户地址服务接口可用性测试(测试用例)
9.9.1.1 用户账户微服务环境可用性校验
package com.edcode.commerce;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 用户账户微服务环境可用性校验
*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class AccountApplicationTest {
@Test
public void contextLoad() {
}
}
9.9.1.2 测试用例基类, 填充登录用户信息
package com.edcode.commerce.service;
import com.edcode.commerce.filter.AccessContext;
import com.edcode.commerce.vo.LoginUserInfo;
import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 测试用例基类, 填充登录用户信息
*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class BaseTest {
protected final LoginUserInfo loginUserInfo = new LoginUserInfo(10L,"eddie_k2@qq.com");
@Before
public void init() {
AccessContext.setLoginUserInfo(loginUserInfo);
}
@After
public void destroy() {
AccessContext.clearLoginUserInfo();
}
}
9.9.1.3 用户地址相关服务功能测试
package com.edcode.commerce.service;
import com.alibaba.fastjson.JSON;
import com.edcode.commerce.account.AddressInfo;
import com.edcode.commerce.common.TableId;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collections;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 用户地址相关服务功能测试
*/
@Slf4j
public class AddressServiceTest extends BaseTest {
@Autowired
private IAddressService addressService;
/**
* 测试创建用户地址信息
*/
@Test
public void testCreateAddressInfo() {
AddressInfo.AddressItem addressItem = new AddressInfo.AddressItem();
addressItem.setUsername("EddieTest");
addressItem.setPhone("12345678910");
addressItem.setProvince("广东省");
addressItem.setCity("广州市");
addressItem.setAddressDetail("广州塔");
log.info("测试创建地址信息: [{}]", JSON.toJSONString(addressService.createAddressInfo(
new AddressInfo(loginUserInfo.getId(), Collections.singletonList(addressItem))
)));
}
/**
* 测试获取当前登录用户地址信息
*/
@Test
public void testGetCurrentAddressInfo() {
log.info("测试获取当前用户信息: [{}]", JSON.toJSONString(
addressService.getCurrentAddressInfo()
));
}
/**
* 测试通过 id 获取用户地址信息
*/
@Test
public void testGetAddressInfoById() {
log.info("测试按id获取地址信息: [{}]", JSON.toJSONString(
addressService.getAddressInfoById(1L)
));
}
/**
* 测试通过 TableId 获取用户地址信息
*/
@Test
public void testGetAddressInfoByTableId() {
log.info("测试按表id获取地址信息: [{}]", JSON.toJSONString(
addressService.getAddressInfoByTableId(
new TableId(Collections.singletonList(new TableId.Id(1L)))
)
));
}
}
9.10.1 用户余额相关服务接口实现
package com.edcode.commerce.service;
import com.edcode.commerce.account.BalanceInfo;
import com.edcode.commerce.dao.ScaCommerceBalanceDao;
import com.edcode.commerce.entity.ScaCommerceBalance;
import com.edcode.commerce.filter.AccessContext;
import com.edcode.commerce.vo.LoginUserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 用于余额相关服务接口实现
*/
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class BalanceServiceImpl implements IBalanceService {
private final ScaCommerceBalanceDao balanceDao;
public BalanceServiceImpl(ScaCommerceBalanceDao balanceDao) {
this.balanceDao = balanceDao;
}
@Override
public BalanceInfo getCurrentUserBalanceInfo() {
LoginUserInfo loginUserInfo = AccessContext.getLoginUserInfo();
BalanceInfo balanceInfo = new BalanceInfo(
loginUserInfo.getId(), 0L
);
ScaCommerceBalance scaCommerceBalance = balanceDao.findByUserId(loginUserInfo.getId());
if (null != scaCommerceBalance) {
balanceInfo.setBalance(scaCommerceBalance.getBalance());
} else {
// 如果还没有用户余额记录, 这里创建出来,余额设定为0即可 (懒加载的思想)
ScaCommerceBalance newBalance = new ScaCommerceBalance();
newBalance.setUserId(loginUserInfo.getId());
newBalance.setBalance(0L);
log.info("初始化用户余额记录: [{}]", balanceDao.save(newBalance).getId());
}
return balanceInfo;
}
@Override
public BalanceInfo deductBalance(BalanceInfo balanceInfo) {
LoginUserInfo loginUserInfo = AccessContext.getLoginUserInfo();
// 扣减用户余额的一个基本原则: 扣减额 <= 当前用户余额
ScaCommerceBalance scaCommerceBalance = balanceDao.findByUserId(loginUserInfo.getId());
// 当前余额 减去 传入的用户余额 小于 0 就抛出异常
if (null == scaCommerceBalance || scaCommerceBalance.getBalance() - balanceInfo.getBalance() < 0) {
throw new RuntimeException("用户余额不足!");
}
// 重新设置扣减之后的余额
Long sourceBalance = scaCommerceBalance.getBalance();
scaCommerceBalance.setBalance(scaCommerceBalance.getBalance() - balanceInfo.getBalance());
// 重新设置后保存到数据库
ScaCommerceBalance scaCommerceBalance1 = balanceDao.save(scaCommerceBalance);
log.info("扣除余额: [{}], [{}], [{}]",
// 获取保存之后的主键id
scaCommerceBalance1.getId(),
// 源余额
sourceBalance,
balanceInfo.getBalance());
return new BalanceInfo(
scaCommerceBalance.getUserId(),
scaCommerceBalance.getBalance()
);
}
}
9.11.1 用户余额服务接口可用性测试(测试用例)
9.11.1.1 用于余额相关服务测试
package com.edcode.commerce.service;
import com.alibaba.fastjson.JSON;
import com.edcode.commerce.account.BalanceInfo;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 用于余额相关服务测试
*/
@Slf4j
public class BalanceServiceTest extends BaseTest {
@Autowired
private IBalanceService balanceService;
/**
* 测试获取当前用户的余额信息
*/
@Test
public void testGetCurrentUserBalanceInfo() {
log.info("测试获取当前用户余额信息: [{}]",
JSON.toJSONString(balanceService.getCurrentUserBalanceInfo()));
}
/**
* 测试扣减用于余额
*/
@Test
public void testDeductBalance() {
BalanceInfo balanceInfo = new BalanceInfo();
balanceInfo.setUserId(loginUserInfo.getId());
balanceInfo.setBalance(1000L);
log.info("测试扣除余额: [{}]", JSON.toJSONString(balanceService.deductBalance(balanceInfo)));
}
}
9.11.1.2 测试扣减用于余额需要修改数据库
- 在第一次创建一条余额数据的时候,Balance 为零
- 在【测试扣减余额】的时候,会提示:“用户余额不足!”
- 需要修改余额比你扣减的余额大
update t_scacommerce_balance
set balance = 10000
where id = 1;
最终效果
9.12.1 用户账户微服务对外HTTP接口
9.12.1.1 用户地址服务 - Controller
package com.edcode.commerce.controller;
import com.edcode.commerce.account.AddressInfo;
import com.edcode.commerce.common.TableId;
import com.edcode.commerce.service.IAddressService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 用户地址服务
*/
@Api(tags = "用户地址服务")
@Slf4j
@RestController
@RequestMapping("/address")
@RequiredArgsConstructor
public class AddressController {
private final IAddressService addressService;
/**
* value 是简述, notes 是详细的描述信息
*/
@ApiOperation(value = "创建", notes = "创建用户地址信息", httpMethod = "POST")
@PostMapping("/create-address")
public TableId createAddressInfo(@RequestBody AddressInfo addressInfo) {
return addressService.createAddressInfo(addressInfo);
}
@ApiOperation(value = "当前用户", notes = "获取当前登录用户地址信息", httpMethod = "GET")
@GetMapping("/current-address")
public AddressInfo getCurrentAddressInfo() {
return addressService.getCurrentAddressInfo();
}
@ApiOperation(value = "获取用户地址信息", notes = "通过 id 获取用户地址信息, id 是 ScaCommerceAddress 表的主键", httpMethod = "GET")
@GetMapping("/address-info")
public AddressInfo getAddressInfoById(@RequestParam Long id) {
return addressService.getAddressInfoById(id);
}
@ApiOperation(value = "获取用户地址信息", notes = "通过 TableId 获取用户地址信息", httpMethod = "POST")
@PostMapping("/address-info-by-table-id")
public AddressInfo getAddressInfoByTablesId(@RequestBody TableId tableId) {
return addressService.getAddressInfoByTableId(tableId);
}
}
9.12.1.2 用户余额服务 - Controller
package com.edcode.commerce.controller;
import com.edcode.commerce.account.BalanceInfo;
import com.edcode.commerce.service.IBalanceService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* @author eddie.lee
* @blog blog.eddilee.cn
* @description 用户余额服务
*/
@Api(tags = "用户余额服务")
@Slf4j
@RestController
@RequestMapping("/balance")
@RequiredArgsConstructor
public class BalanceController {
private final IBalanceService balanceService;
@ApiOperation(value = "当前用户", notes = "获取当前用户余额信息", httpMethod = "GET")
@GetMapping("/current-balance")
public BalanceInfo getCurrentUserBalanceInfo() {
return balanceService.getCurrentUserBalanceInfo();
}
@ApiOperation(value = "扣减", notes = "扣减用于余额", httpMethod = "PUT")
@PutMapping("/deduct-balance")
public BalanceInfo deductBalance(@RequestBody BalanceInfo balanceInfo) {
return balanceService.deductBalance(balanceInfo);
}
}
9.13.1 验证用户账户微服务功能可用性(上)
9.13.1.1 account-address.http
### 创建用户地址信息
POST http://127.0.0.1:9001/edcode/scacommerce-account-service/address/create-address
Content-Type: application/json
sca-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJzY2EtY29tbWVyY2UtdXNlciI6IntcImlkXCI6MTEsXCJ1c2VybmFtZVwiOlwiZWRkaWVAcXEuY29tXCJ9IiwianRpIjoiNGNiMTg2NWYtN2EzMy00MjY2LWJiYjctYjdlZDY2NWZiZTAwIiwiZXhwIjoxNjM2NDczNjAwfQ.SUoIOcN5o9na1YKHeK0zTx2IWXjxIvu5Kkw3ndh7zLz61G1d-KJv3Ncmpi-PsALFWcQBxoWouZQIWoIaCufwsBLMCqP0xFD-iGdxcDK-pz03TOQgGwj4ncOFFdpoR3T3Qy-mnUTb2BJK18pC322GcilVO1aBlQ2aAFas78_bJe8nHEibetBJ68i5zRHX6jG3DAd33Lry1cUaQIAdimwBCaSld9bAv6dk3qpgzIs6AakDz_ctogSDhYCWT3yG2NRwGa4CSrz2QxxjcP-46FtGFty9w_EHJxP7h8QTMQs5JIFI7oQxFujEE9iMGASZsRy2Hd2iDj9rzlQlX5Cs9jpAnw
{
"userId": 10,
"addressItems": [
{
"username": "Eddie_Lee",
"phone": "12345678910",
"province": "广东省",
"city": "广州市",
"addressDetail": "天河区"
}
]
}
### 当前登录用户地址信息
GET http://127.0.0.1:9001/edcode/scacommerce-account-service/address/current-address
Accept: application/json
sca-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJzY2EtY29tbWVyY2UtdXNlciI6IntcImlkXCI6MTEsXCJ1c2VybmFtZVwiOlwiZWRkaWVAcXEuY29tXCJ9IiwianRpIjoiNGNiMTg2NWYtN2EzMy00MjY2LWJiYjctYjdlZDY2NWZiZTAwIiwiZXhwIjoxNjM2NDczNjAwfQ.SUoIOcN5o9na1YKHeK0zTx2IWXjxIvu5Kkw3ndh7zLz61G1d-KJv3Ncmpi-PsALFWcQBxoWouZQIWoIaCufwsBLMCqP0xFD-iGdxcDK-pz03TOQgGwj4ncOFFdpoR3T3Qy-mnUTb2BJK18pC322GcilVO1aBlQ2aAFas78_bJe8nHEibetBJ68i5zRHX6jG3DAd33Lry1cUaQIAdimwBCaSld9bAv6dk3qpgzIs6AakDz_ctogSDhYCWT3yG2NRwGa4CSrz2QxxjcP-46FtGFty9w_EHJxP7h8QTMQs5JIFI7oQxFujEE9iMGASZsRy2Hd2iDj9rzlQlX5Cs9jpAnw
### 通过 id 获取用户地址信息
GET http://127.0.0.1:9001/edcode/scacommerce-account-service/address/address-info?id=1
Accept: application/json
sca-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJzY2EtY29tbWVyY2UtdXNlciI6IntcImlkXCI6MTEsXCJ1c2VybmFtZVwiOlwiZWRkaWVAcXEuY29tXCJ9IiwianRpIjoiNGNiMTg2NWYtN2EzMy00MjY2LWJiYjctYjdlZDY2NWZiZTAwIiwiZXhwIjoxNjM2NDczNjAwfQ.SUoIOcN5o9na1YKHeK0zTx2IWXjxIvu5Kkw3ndh7zLz61G1d-KJv3Ncmpi-PsALFWcQBxoWouZQIWoIaCufwsBLMCqP0xFD-iGdxcDK-pz03TOQgGwj4ncOFFdpoR3T3Qy-mnUTb2BJK18pC322GcilVO1aBlQ2aAFas78_bJe8nHEibetBJ68i5zRHX6jG3DAd33Lry1cUaQIAdimwBCaSld9bAv6dk3qpgzIs6AakDz_ctogSDhYCWT3yG2NRwGa4CSrz2QxxjcP-46FtGFty9w_EHJxP7h8QTMQs5JIFI7oQxFujEE9iMGASZsRy2Hd2iDj9rzlQlX5Cs9jpAnw
### 获取用户地址信息
POST http://127.0.0.1:9001/edcode/scacommerce-account-service/address/address-info-by-table-id
Content-Type: application/json
sca-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJzY2EtY29tbWVyY2UtdXNlciI6IntcImlkXCI6MTEsXCJ1c2VybmFtZVwiOlwiZWRkaWVAcXEuY29tXCJ9IiwianRpIjoiNGNiMTg2NWYtN2EzMy00MjY2LWJiYjctYjdlZDY2NWZiZTAwIiwiZXhwIjoxNjM2NDczNjAwfQ.SUoIOcN5o9na1YKHeK0zTx2IWXjxIvu5Kkw3ndh7zLz61G1d-KJv3Ncmpi-PsALFWcQBxoWouZQIWoIaCufwsBLMCqP0xFD-iGdxcDK-pz03TOQgGwj4ncOFFdpoR3T3Qy-mnUTb2BJK18pC322GcilVO1aBlQ2aAFas78_bJe8nHEibetBJ68i5zRHX6jG3DAd33Lry1cUaQIAdimwBCaSld9bAv6dk3qpgzIs6AakDz_ctogSDhYCWT3yG2NRwGa4CSrz2QxxjcP-46FtGFty9w_EHJxP7h8QTMQs5JIFI7oQxFujEE9iMGASZsRy2Hd2iDj9rzlQlX5Cs9jpAnw
{
"ids": [
{
"id": 1
},
{
"id": 2
}
]
}
###
- sca-commerce-user 定义是 Token key 在 CommonConstant 里面
- 清空一下数据库,保持干净状态 TRUNCATE
xxxx
- 如果出现 401 之类的,通常是 token过期,在请求一次:src/main/resources/http/authority.http
9.14.1 验证用户账户微服务功能可用性(下)
9.14.1.1 account-balance.http
### com.edcode.commerce.constant.CommonConstant.JWT_USER_INFO_KEY = sca-commerce-user
### TRUNCATE `t_scacommerce_balance`
### 获取当前用户余额信息
GET http://127.0.0.1:9001/edcode/scacommerce-account-service/balance/current-balance
Accept: application/json
sca-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJzY2EtY29tbWVyY2UtdXNlciI6IntcImlkXCI6MTEsXCJ1c2VybmFtZVwiOlwiZWRkaWVAcXEuY29tXCJ9IiwianRpIjoiNGNiMTg2NWYtN2EzMy00MjY2LWJiYjctYjdlZDY2NWZiZTAwIiwiZXhwIjoxNjM2NDczNjAwfQ.SUoIOcN5o9na1YKHeK0zTx2IWXjxIvu5Kkw3ndh7zLz61G1d-KJv3Ncmpi-PsALFWcQBxoWouZQIWoIaCufwsBLMCqP0xFD-iGdxcDK-pz03TOQgGwj4ncOFFdpoR3T3Qy-mnUTb2BJK18pC322GcilVO1aBlQ2aAFas78_bJe8nHEibetBJ68i5zRHX6jG3DAd33Lry1cUaQIAdimwBCaSld9bAv6dk3qpgzIs6AakDz_ctogSDhYCWT3yG2NRwGa4CSrz2QxxjcP-46FtGFty9w_EHJxP7h8QTMQs5JIFI7oQxFujEE9iMGASZsRy2Hd2iDj9rzlQlX5Cs9jpAnw
### 扣减用户余额
PUT http://127.0.0.1:9001/edcode/scacommerce-account-service/balance/deduct-balance
Content-Type: application/json
sca-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJzY2EtY29tbWVyY2UtdXNlciI6IntcImlkXCI6MTEsXCJ1c2VybmFtZVwiOlwiZWRkaWVAcXEuY29tXCJ9IiwianRpIjoiNGNiMTg2NWYtN2EzMy00MjY2LWJiYjctYjdlZDY2NWZiZTAwIiwiZXhwIjoxNjM2NDczNjAwfQ.SUoIOcN5o9na1YKHeK0zTx2IWXjxIvu5Kkw3ndh7zLz61G1d-KJv3Ncmpi-PsALFWcQBxoWouZQIWoIaCufwsBLMCqP0xFD-iGdxcDK-pz03TOQgGwj4ncOFFdpoR3T3Qy-mnUTb2BJK18pC322GcilVO1aBlQ2aAFas78_bJe8nHEibetBJ68i5zRHX6jG3DAd33Lry1cUaQIAdimwBCaSld9bAv6dk3qpgzIs6AakDz_ctogSDhYCWT3yG2NRwGa4CSrz2QxxjcP-46FtGFty9w_EHJxP7h8QTMQs5JIFI7oQxFujEE9iMGASZsRy2Hd2iDj9rzlQlX5Cs9jpAnw
{
"userId": 10,
"balance": 2000
}
###
9.15.1 用户账户微服务总结
9.15.1.1 用户账户微服务总结
- 微服务开始之前的准备工作
- 用户身份登录拦截
- 在请求进入 service 之前解析 header 中的 token 信息,并填充用户信息到上下文中
- 在请求结束之后,清理掉上下文中的用户信息
- 对于一些特定的 HTTP 请求不要拦截(即白名单)
- 代码即文档:引入 Swagger
- pom.xml 中添加依赖配置
- 自定义配置 Swagger
- 用户身份登录拦截
9.15.1.2 微服务模块的设计思想
- 微服务模块应该是低耦合,尽可能多的重用代码