diff --git a/dev-protocol-springcloud/SpringCloud项目介绍.md b/dev-protocol-springcloud/SpringCloud项目介绍.md
index 486c1de..a944c0c 100644
--- a/dev-protocol-springcloud/SpringCloud项目介绍.md
+++ b/dev-protocol-springcloud/SpringCloud项目介绍.md
@@ -22,9 +22,10 @@
## 通用工程模块
+- 通用服务 - [身份拦截]
+ -
- 授权、鉴权中心微服务
- [dev-protocol-springcloud-project-authority-center](dev-protocol-springcloud-project-authority-center)
-
- 用户账户微服务
- 商品微服务
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-gateway/README-SpringCloud-Gateway.md b/dev-protocol-springcloud/dev-protocol-springcloud-gateway/README-SpringCloud-Gateway.md
index ab4a8fb..7cf529e 100644
--- a/dev-protocol-springcloud/dev-protocol-springcloud-gateway/README-SpringCloud-Gateway.md
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-gateway/README-SpringCloud-Gateway.md
@@ -78,7 +78,7 @@
"name": "Path"
}
],
- "uri": "lb://dev-protocol-spring-cloud-nacos"
+ "uri": "lb://dev-protocol-spring-cloud-nacos",
"filters": [
{
"name": "HeaderToken"
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/pic/用户账户微服务总设计.png b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/pic/用户账户微服务总设计.png
new file mode 100644
index 0000000..649ba51
Binary files /dev/null and b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/pic/用户账户微服务总设计.png differ
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/pic/账户微服务在业务中的位置.png b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/pic/账户微服务在业务中的位置.png
new file mode 100644
index 0000000..560bc34
Binary files /dev/null and b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/pic/账户微服务在业务中的位置.png differ
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/pom.xml b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/pom.xml
new file mode 100644
index 0000000..1052b55
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/pom.xml
@@ -0,0 +1,98 @@
+
+
+ 4.0.0
+
+ org.example
+ dev-protocol
+ 1.0-SNAPSHOT
+ ../../pom.xml
+
+
+ dev-protocol-springcloud-project-account-service
+
+ dev-protocol-springcloud-project-account-service
+ 账户服务
+ jar
+
+
+ 8
+ 8
+ UTF-8
+
+
+
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-zipkin
+
+
+ org.springframework.kafka
+ spring-kafka
+ 2.5.0.RELEASE
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ mysql
+ mysql-connector-java
+ 8.0.12
+ runtime
+
+
+ org.example
+ dev-protocol-springcloud-project-service-config
+ 1.0-SNAPSHOT
+
+
+ org.example
+ dev-protocol-springcloud-project-service-sdk
+ 1.0-SNAPSHOT
+
+
+ org.apache.commons
+ commons-lang3
+ 3.12.0
+
+
+ org.apache.commons
+ commons-collections4
+ 4.4
+
+
+
+
+
+ ${artifactId}
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/AccountApplication.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/AccountApplication.java
new file mode 100644
index 0000000..d3a87fe
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/AccountApplication.java
@@ -0,0 +1,20 @@
+package org.example;
+
+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;
+
+/**
+ *
用户账户微服务启动入口
+ * 127.0.0.1:8003/dev-protocol-springcloud-project-account-service/swagger-ui.html 原生的地址信息
+ * 127.0.0.1:8003/dev-protocol-springcloud-project-account-service/doc.html 美化之后的地址信息
+ * */
+@EnableJpaAuditing
+@SpringBootApplication
+@EnableDiscoveryClient
+public class AccountApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(AccountApplication.class, args);
+ }
+}
\ No newline at end of file
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/controller/AddressController.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/controller/AddressController.java
new file mode 100644
index 0000000..6e45322
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/controller/AddressController.java
@@ -0,0 +1,58 @@
+package org.example.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.example.account.AddressInfo;
+import org.example.common.TableId;
+import org.example.service.IAddressService;
+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;
+
+/**
+ * 用户地址服务 Controller
+ * */
+@Api(tags = "用户地址服务")
+@Slf4j
+@RestController
+@RequestMapping("/address")
+public class AddressController {
+
+ private final IAddressService addressService;
+
+ public AddressController(IAddressService addressService) {
+ this.addressService = 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 是 Address 表的主键",
+ 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);
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/controller/BalanceController.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/controller/BalanceController.java
new file mode 100644
index 0000000..a344f56
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/controller/BalanceController.java
@@ -0,0 +1,40 @@
+package org.example.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.example.account.BalanceInfo;
+import org.example.service.IBalanceService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 用户余额服务 Controller
+ * */
+@Api(tags = "用户余额服务")
+@Slf4j
+@RestController
+@RequestMapping("/balance")
+public class BalanceController {
+
+ private final IBalanceService balanceService;
+
+ public BalanceController(IBalanceService balanceService) {
+ this.balanceService = 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);
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/dao/EcommerceAddressDao.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/dao/EcommerceAddressDao.java
new file mode 100644
index 0000000..acb1baa
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/dao/EcommerceAddressDao.java
@@ -0,0 +1,17 @@
+package org.example.dao;
+
+import org.example.entity.Address;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+/**
+ * EcommerceAddress Dao 接口定义
+ * */
+public interface EcommerceAddressDao extends JpaRepository {
+
+ /**
+ * 根据 用户 id 查询地址信息
+ * */
+ List findAllByUserId(Long userId);
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/dao/EcommerceBalanceDao.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/dao/EcommerceBalanceDao.java
new file mode 100644
index 0000000..d69944e
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/dao/EcommerceBalanceDao.java
@@ -0,0 +1,13 @@
+package org.example.dao;
+
+import org.example.entity.Balance;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+/**
+ * Balance Dao 接口定义
+ * */
+public interface EcommerceBalanceDao extends JpaRepository {
+
+ /** 根据 userId 查询 EcommerceBalance 对象 */
+ Balance findByUserId(Long userId);
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/entity/Address.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/entity/Address.java
new file mode 100644
index 0000000..4f097d3
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/entity/Address.java
@@ -0,0 +1,106 @@
+package org.example.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.example.account.AddressInfo;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EntityListeners;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.util.Date;
+
+/**
+ * 用户地址表实体类定义
+ * */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@EntityListeners(AuditingEntityListener.class)
+@Table(name = "t_dev_protocol_cloud_address")
+public class Address {
+
+ /** 自增主键 */
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id", nullable = false)
+ 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;
+
+ /** 创建时间 */
+ @CreatedDate
+ @Column(name = "create_time", nullable = false)
+ private Date createTime;
+
+ /** 更新时间 */
+ @LastModifiedDate
+ @Column(name = "update_time", nullable = false)
+ private Date updateTime;
+
+ /**
+ * 根据 userId + AddressItem 得到 Address
+ * */
+ public static Address to(Long userId, AddressInfo.AddressItem addressItem) {
+
+ Address ecommerceAddress = new Address();
+
+ ecommerceAddress.setUserId(userId);
+ ecommerceAddress.setUsername(addressItem.getUsername());
+ ecommerceAddress.setPhone(addressItem.getPhone());
+ ecommerceAddress.setProvince(addressItem.getProvince());
+ ecommerceAddress.setCity(addressItem.getCity());
+ ecommerceAddress.setAddressDetail(addressItem.getAddressDetail());
+
+ return ecommerceAddress;
+ }
+
+ /**
+ * 将 Address 对象转成 AddressInfo
+ * */
+ public AddressInfo.AddressItem toAddressItem() {
+
+ AddressInfo.AddressItem addressItem = new AddressInfo.AddressItem();
+
+ addressItem.setId(this.id);
+ addressItem.setUsername(this.username);
+ addressItem.setPhone(this.phone);
+ addressItem.setProvince(this.province);
+ addressItem.setCity(this.city);
+ addressItem.setAddressDetail(this.addressDetail);
+ addressItem.setCreateTime(this.createTime);
+ addressItem.setUpdateTime(this.updateTime);
+
+ return addressItem;
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/entity/Balance.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/entity/Balance.java
new file mode 100644
index 0000000..7225a32
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/entity/Balance.java
@@ -0,0 +1,53 @@
+package org.example.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EntityListeners;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.util.Date;
+
+/**
+ * 用户账户余额表实体类定义
+ * */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@EntityListeners(AuditingEntityListener.class)
+@Table(name = "t_dev_protocol_cloud_balance")
+public class Balance {
+
+ /** 自增主键 */
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id", nullable = false)
+ private Long id;
+
+ /** 用户 id */
+ @Column(name = "user_id", nullable = false)
+ private Long userId;
+
+ /** 账户余额 */
+ @Column(name = "balance", nullable = false)
+ private Long balance;
+
+ /** 创建时间 */
+ @CreatedDate
+ @Column(name = "create_time", nullable = false)
+ private Date createTime;
+
+ /** 更新时间 */
+ @LastModifiedDate
+ @Column(name = "update_time", nullable = false)
+ private Date updateTime;
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/service/IAddressService.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/service/IAddressService.java
new file mode 100644
index 0000000..01894cc
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/service/IAddressService.java
@@ -0,0 +1,31 @@
+package org.example.service;
+
+
+import org.example.account.AddressInfo;
+import org.example.common.TableId;
+
+/**
+ * 用户地址相关服务接口定义
+ * */
+public interface IAddressService {
+
+ /**
+ * 创建用户地址信息
+ * */
+ TableId createAddressInfo(AddressInfo addressInfo);
+
+ /**
+ * 获取当前登录的用户地址信息
+ * */
+ AddressInfo getCurrentAddressInfo();
+
+ /**
+ * 通过 id 获取用户地址信息, id 是 Address 表的主键
+ * */
+ AddressInfo getAddressInfoById(Long id);
+
+ /**
+ * 通过 TableId 获取用户地址信息
+ * */
+ AddressInfo getAddressInfoByTableId(TableId tableId);
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/service/IBalanceService.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/service/IBalanceService.java
new file mode 100644
index 0000000..300ebab
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/service/IBalanceService.java
@@ -0,0 +1,21 @@
+package org.example.service;
+
+
+import org.example.account.BalanceInfo;
+
+/**
+ * 用于余额相关的服务接口定义
+ * */
+public interface IBalanceService {
+
+ /**
+ * 获取当前用户余额信息
+ * */
+ BalanceInfo getCurrentUserBalanceInfo();
+
+ /**
+ * 扣减用户余额
+ * @param balanceInfo 代表想要扣减的余额
+ * */
+ BalanceInfo deductBalance(BalanceInfo balanceInfo);
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/service/impl/AddressServiceImpl.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/service/impl/AddressServiceImpl.java
new file mode 100644
index 0000000..1fc0dd6
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/service/impl/AddressServiceImpl.java
@@ -0,0 +1,110 @@
+package org.example.service.impl;
+
+import com.alibaba.fastjson2.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.example.account.AddressInfo;
+import org.example.common.TableId;
+import org.example.dao.EcommerceAddressDao;
+import org.example.entity.Address;
+import org.example.filter.AccessContext;
+import org.example.service.IAddressService;
+import org.example.vo.LoginUserInfo;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 用户地址相关服务接口实现
+ * */
+@Slf4j
+@Service
+@Transactional(rollbackFor = Exception.class)
+public class AddressServiceImpl implements IAddressService {
+
+ private final EcommerceAddressDao addressDao;
+
+ public AddressServiceImpl(EcommerceAddressDao addressDao) {
+ this.addressDao = addressDao;
+ }
+
+ /**
+ * 存储多个地址信息
+ * */
+ @Override
+ public TableId createAddressInfo(AddressInfo addressInfo) {
+
+ // 不能直接从参数中获取用户的 id 信息
+ LoginUserInfo loginUserInfo = AccessContext.getLoginUserInfo();
+
+ // 将传递的参数转换成实体对象
+ List ecommerceAddresses = addressInfo.getAddressItems().stream()
+ .map(a -> Address.to(loginUserInfo.getId(), a))
+ .collect(Collectors.toList());
+
+ // 保存到数据表并把返回记录的 id 给调用方
+ List savedRecords = addressDao.saveAll(ecommerceAddresses);
+ List ids = savedRecords.stream()
+ .map(Address::getId).collect(Collectors.toList());
+ log.info("create address info: [{}], [{}]", loginUserInfo.getId(),
+ JSON.toJSONString(ids));
+
+ return new TableId(
+ ids.stream().map(TableId.Id::new).collect(Collectors.toList())
+ );
+ }
+
+ @Override
+ public AddressInfo getCurrentAddressInfo() {
+
+ LoginUserInfo loginUserInfo = AccessContext.getLoginUserInfo();
+
+ // 根据 userId 查询到用户的地址信息, 再实现转换
+ List ecommerceAddresses = addressDao.findAllByUserId(
+ loginUserInfo.getId()
+ );
+ List addressItems = ecommerceAddresses.stream()
+ .map(Address::toAddressItem)
+ .collect(Collectors.toList());
+
+ return new AddressInfo(loginUserInfo.getId(), addressItems);
+ }
+
+ @Override
+ public AddressInfo getAddressInfoById(Long id) {
+
+ Address ecommerceAddress = addressDao.findById(id).orElse(null);
+ if (null == ecommerceAddress) {
+ throw new RuntimeException("address is not exist");
+ }
+
+ return new AddressInfo(
+ ecommerceAddress.getUserId(),
+ Collections.singletonList(ecommerceAddress.toAddressItem())
+ );
+ }
+
+ @Override
+ public AddressInfo getAddressInfoByTableId(TableId tableId) {
+
+ List ids = tableId.getIds().stream()
+ .map(TableId.Id::getId).collect(Collectors.toList());
+ log.info("get address info by table id: [{}]", JSON.toJSONString(ids));
+
+ List ecommerceAddresses = addressDao.findAllById(ids);
+ if (CollectionUtils.isEmpty(ecommerceAddresses)) {
+ return new AddressInfo(-1L, Collections.emptyList());
+ }
+
+ List addressItems = ecommerceAddresses.stream()
+ .map(Address::toAddressItem)
+ .collect(Collectors.toList());
+
+ return new AddressInfo(
+ ecommerceAddresses.get(0).getUserId(), addressItems
+ );
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/service/impl/BalanceServiceImpl.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/service/impl/BalanceServiceImpl.java
new file mode 100644
index 0000000..c8bcd23
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/service/impl/BalanceServiceImpl.java
@@ -0,0 +1,76 @@
+package org.example.service.impl;
+
+import lombok.extern.slf4j.Slf4j;
+import org.example.account.BalanceInfo;
+import org.example.dao.EcommerceBalanceDao;
+import org.example.entity.Balance;
+import org.example.filter.AccessContext;
+import org.example.service.IBalanceService;
+import org.example.vo.LoginUserInfo;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * 用于余额相关服务接口实现
+ * */
+@Slf4j
+@Service
+@Transactional(rollbackFor = Exception.class)
+public class BalanceServiceImpl implements IBalanceService {
+
+ private final EcommerceBalanceDao balanceDao;
+
+ public BalanceServiceImpl(EcommerceBalanceDao balanceDao) {
+ this.balanceDao = balanceDao;
+ }
+
+ @Override
+ public BalanceInfo getCurrentUserBalanceInfo() {
+
+ LoginUserInfo loginUserInfo = AccessContext.getLoginUserInfo();
+ BalanceInfo balanceInfo = new BalanceInfo(
+ loginUserInfo.getId(), 0L
+ );
+
+ Balance ecommerceBalance =
+ balanceDao.findByUserId(loginUserInfo.getId());
+ if (null != ecommerceBalance) {
+ balanceInfo.setBalance(ecommerceBalance.getBalance());
+ } else {
+ // 如果还没有用户余额记录, 这里创建出来,余额设定为0即可
+ Balance newBalance = new Balance();
+ newBalance.setUserId(loginUserInfo.getId());
+ newBalance.setBalance(0L);
+ log.info("init user balance record: [{}]",
+ balanceDao.save(newBalance).getId());
+ }
+
+ return balanceInfo;
+ }
+
+ @Override
+ public BalanceInfo deductBalance(BalanceInfo balanceInfo) {
+
+ LoginUserInfo loginUserInfo = AccessContext.getLoginUserInfo();
+
+ // 扣减用户余额的一个基本原则: 扣减额 <= 当前用户余额
+ Balance ecommerceBalance =
+ balanceDao.findByUserId(loginUserInfo.getId());
+ if (null == ecommerceBalance
+ || ecommerceBalance.getBalance() - balanceInfo.getBalance() < 0
+ ) {
+ throw new RuntimeException("user balance is not enough!");
+ }
+
+ Long sourceBalance = ecommerceBalance.getBalance();
+ ecommerceBalance.setBalance(ecommerceBalance.getBalance() - balanceInfo.getBalance());
+ log.info("deduct balance: [{}], [{}], [{}]",
+ balanceDao.save(ecommerceBalance).getId(), sourceBalance,
+ balanceInfo.getBalance());
+
+ return new BalanceInfo(
+ ecommerceBalance.getUserId(),
+ ecommerceBalance.getBalance()
+ );
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/bootstrap.yml b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/bootstrap.yml
new file mode 100644
index 0000000..1a3b3da
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/bootstrap.yml
@@ -0,0 +1,67 @@
+server:
+ port: 8003
+ servlet:
+ context-path: /dev-protocol-springcloud-project-account-service
+
+spring:
+ application:
+ name: dev-protocol-springcloud-project-account-service
+ cloud:
+ nacos:
+ discovery:
+ enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
+ server-addr: 127.0.0.1:8848
+ # server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850 # Nacos 服务器地址
+ namespace: 1ccc74ae-9398-4dbe-b9d7-4f9addf9f40c
+ metadata:
+ management:
+ context-path: ${server.servlet.context-path}/actuator
+ kafka:
+ bootstrap-servers: 127.0.0.1:9092
+ producer:
+ retries: 3
+ consumer:
+ auto-offset-reset: latest
+ sleuth:
+ sampler:
+ probability: 1.0 # 采样比例, 1.0 表示 100%, 默认是 0.1
+ zipkin:
+ sender:
+ type: kafka # 默认是 web
+ base-url: http://localhost:9411/
+ jpa:
+ show-sql: true
+ hibernate:
+ ddl-auto: none
+ properties:
+ hibernate.show_sql: true
+ hibernate.format_sql: true
+ hibernate:
+ dialect: org.hibernate.dialect.MySQLDialect
+ open-in-view: false
+ datasource:
+ # 数据源
+ url: jdbc:mysql://127.0.0.1:3306/dev_protocol_springcloud_project?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
+ username: root
+ password: root
+ 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: devProtocolSpringcloudHikariCP
+
+# 暴露端点
+management:
+ endpoints:
+ web:
+ exposure:
+ include: '*'
+ endpoint:
+ health:
+ show-details: always
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/http/account-address.http b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/http/account-address.http
new file mode 100644
index 0000000..acd87d4
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/http/account-address.http
@@ -0,0 +1,43 @@
+### 创建用户地址信息
+POST http://127.0.0.1:9001/dev-protocol-springcloud-gateway/dev-protocol-springcloud-project-account-service/address/create-address
+Content-Type: application/json
+e-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEwLFwidXNlcm5hbWVcIjpcIlFpbnlpQGltb29jLmNvbVwifSIsImp0aSI6IjU2ZGViY2NiLTlkNTEtNDMzMC04NjFhLTY5MWI0YWE2NzY1MiIsImV4cCI6MTYyNDcyMzIwMH0.M9Xg__zvXvAeALMcAs1LIJN41_JGo7Od1bw0218AbmzrBMZ-9WT6plow62gSBfxa5Xbm79t6VeHq8iC7a0ZYBF2RemNaXmrgIkmwxjg12jO56hTCRv1jghAbiFZknTxXA4QQ6dB3MS4mE19MDt6P3f3ckLwyl-IjE9O5_c-wg47Sb8odfrm_K2RMx_2ZMQAfNjHzxCzrQyNRWhqSQTpSVAuUScaxlnf4EvEwav8FT0QICIlx8oZyU7fh_zBtfFL4EhQlHfcU-eo6Nw4YUVA0knIrkAXD8jqCowx7Mej8khQ6zXNUB2V_uTtc2P2VW1UHlWSAiFCR_qkvBGSWO0VH_w
+
+{
+ "userId": 10,
+ "addressItems": [
+ {
+ "username": "zxcx",
+ "phone": "16600000001",
+ "province": "上海市",
+ "city": "上海市",
+ "addressDetail": "闵行区"
+ }
+ ]
+}
+
+### 当前登录用户地址信息
+GET http://127.0.0.1:9001/dev-protocol-springcloud-gateway/dev-protocol-springcloud-project-account-service/address/current-address
+Accept: application/json
+e-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEwLFwidXNlcm5hbWVcIjpcIlFpbnlpQGltb29jLmNvbVwifSIsImp0aSI6IjU2ZGViY2NiLTlkNTEtNDMzMC04NjFhLTY5MWI0YWE2NzY1MiIsImV4cCI6MTYyNDcyMzIwMH0.M9Xg__zvXvAeALMcAs1LIJN41_JGo7Od1bw0218AbmzrBMZ-9WT6plow62gSBfxa5Xbm79t6VeHq8iC7a0ZYBF2RemNaXmrgIkmwxjg12jO56hTCRv1jghAbiFZknTxXA4QQ6dB3MS4mE19MDt6P3f3ckLwyl-IjE9O5_c-wg47Sb8odfrm_K2RMx_2ZMQAfNjHzxCzrQyNRWhqSQTpSVAuUScaxlnf4EvEwav8FT0QICIlx8oZyU7fh_zBtfFL4EhQlHfcU-eo6Nw4YUVA0knIrkAXD8jqCowx7Mej8khQ6zXNUB2V_uTtc2P2VW1UHlWSAiFCR_qkvBGSWO0VH_w
+
+### 通过 id 获取用户地址信息
+GET http://127.0.0.1:9001/dev-protocol-springcloud-gateway/dev-protocol-springcloud-project-account-service/address/address-info?id=2
+Accept: application/json
+e-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEwLFwidXNlcm5hbWVcIjpcIlFpbnlpQGltb29jLmNvbVwifSIsImp0aSI6IjU2ZGViY2NiLTlkNTEtNDMzMC04NjFhLTY5MWI0YWE2NzY1MiIsImV4cCI6MTYyNDcyMzIwMH0.M9Xg__zvXvAeALMcAs1LIJN41_JGo7Od1bw0218AbmzrBMZ-9WT6plow62gSBfxa5Xbm79t6VeHq8iC7a0ZYBF2RemNaXmrgIkmwxjg12jO56hTCRv1jghAbiFZknTxXA4QQ6dB3MS4mE19MDt6P3f3ckLwyl-IjE9O5_c-wg47Sb8odfrm_K2RMx_2ZMQAfNjHzxCzrQyNRWhqSQTpSVAuUScaxlnf4EvEwav8FT0QICIlx8oZyU7fh_zBtfFL4EhQlHfcU-eo6Nw4YUVA0knIrkAXD8jqCowx7Mej8khQ6zXNUB2V_uTtc2P2VW1UHlWSAiFCR_qkvBGSWO0VH_w
+
+### 获取用户地址信息
+POST http://127.0.0.1:9001/dev-protocol-springcloud-gateway/dev-protocol-springcloud-project-account-service/address/address-info-by-table-id
+Content-Type: application/json
+e-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEwLFwidXNlcm5hbWVcIjpcIlFpbnlpQGltb29jLmNvbVwifSIsImp0aSI6IjU2ZGViY2NiLTlkNTEtNDMzMC04NjFhLTY5MWI0YWE2NzY1MiIsImV4cCI6MTYyNDcyMzIwMH0.M9Xg__zvXvAeALMcAs1LIJN41_JGo7Od1bw0218AbmzrBMZ-9WT6plow62gSBfxa5Xbm79t6VeHq8iC7a0ZYBF2RemNaXmrgIkmwxjg12jO56hTCRv1jghAbiFZknTxXA4QQ6dB3MS4mE19MDt6P3f3ckLwyl-IjE9O5_c-wg47Sb8odfrm_K2RMx_2ZMQAfNjHzxCzrQyNRWhqSQTpSVAuUScaxlnf4EvEwav8FT0QICIlx8oZyU7fh_zBtfFL4EhQlHfcU-eo6Nw4YUVA0knIrkAXD8jqCowx7Mej8khQ6zXNUB2V_uTtc2P2VW1UHlWSAiFCR_qkvBGSWO0VH_w
+
+{
+ "ids": [
+ {
+ "id": 1
+ },
+ {
+ "id": 2
+ }
+ ]
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/http/account-balance.http b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/http/account-balance.http
new file mode 100644
index 0000000..3dab2e4
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/http/account-balance.http
@@ -0,0 +1,14 @@
+### 获取当前用户余额信息
+GET http://127.0.0.1:9001/dev-protocol-springcloud-gateway/dev-protocol-springcloud-project-account-service/balance/current-balance
+Accept: application/json
+e-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEwLFwidXNlcm5hbWVcIjpcIlFpbnlpQGltb29jLmNvbVwifSIsImp0aSI6IjU2ZGViY2NiLTlkNTEtNDMzMC04NjFhLTY5MWI0YWE2NzY1MiIsImV4cCI6MTYyNDcyMzIwMH0.M9Xg__zvXvAeALMcAs1LIJN41_JGo7Od1bw0218AbmzrBMZ-9WT6plow62gSBfxa5Xbm79t6VeHq8iC7a0ZYBF2RemNaXmrgIkmwxjg12jO56hTCRv1jghAbiFZknTxXA4QQ6dB3MS4mE19MDt6P3f3ckLwyl-IjE9O5_c-wg47Sb8odfrm_K2RMx_2ZMQAfNjHzxCzrQyNRWhqSQTpSVAuUScaxlnf4EvEwav8FT0QICIlx8oZyU7fh_zBtfFL4EhQlHfcU-eo6Nw4YUVA0knIrkAXD8jqCowx7Mej8khQ6zXNUB2V_uTtc2P2VW1UHlWSAiFCR_qkvBGSWO0VH_w
+
+### 扣减用户余额
+PUT http://127.0.0.1:9001/dev-protocol-springcloud-gateway/dev-protocol-springcloud-project-account-service/balance/deduct-balance
+Content-Type: application/json
+e-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEwLFwidXNlcm5hbWVcIjpcIlFpbnlpQGltb29jLmNvbVwifSIsImp0aSI6IjU2ZGViY2NiLTlkNTEtNDMzMC04NjFhLTY5MWI0YWE2NzY1MiIsImV4cCI6MTYyNDcyMzIwMH0.M9Xg__zvXvAeALMcAs1LIJN41_JGo7Od1bw0218AbmzrBMZ-9WT6plow62gSBfxa5Xbm79t6VeHq8iC7a0ZYBF2RemNaXmrgIkmwxjg12jO56hTCRv1jghAbiFZknTxXA4QQ6dB3MS4mE19MDt6P3f3ckLwyl-IjE9O5_c-wg47Sb8odfrm_K2RMx_2ZMQAfNjHzxCzrQyNRWhqSQTpSVAuUScaxlnf4EvEwav8FT0QICIlx8oZyU7fh_zBtfFL4EhQlHfcU-eo6Nw4YUVA0knIrkAXD8jqCowx7Mej8khQ6zXNUB2V_uTtc2P2VW1UHlWSAiFCR_qkvBGSWO0VH_w
+
+{
+ "userId": 10,
+ "balance": 2000
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/sql/t_ecommerce_address.sql b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/sql/t_ecommerce_address.sql
new file mode 100644
index 0000000..c8b4e13
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/sql/t_ecommerce_address.sql
@@ -0,0 +1,13 @@
+-- 创建 t_ecommerce_address 数据表
+CREATE TABLE IF NOT EXISTS `dev_protocol_springcloud_project`.`t_dev_protocol_cloud_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=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户地址表';
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/sql/t_ecommerce_balance.sql b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/sql/t_ecommerce_balance.sql
new file mode 100644
index 0000000..2767a8b
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/sql/t_ecommerce_balance.sql
@@ -0,0 +1,10 @@
+-- 创建 t_ecommerce_balance 数据表
+CREATE TABLE IF NOT EXISTS `dev_protocol_springcloud_project`.`t_dev_protocol_cloud_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=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户账户余额表';
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/test/java/org/example/AccountApplicationTest.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/test/java/org/example/AccountApplicationTest.java
new file mode 100644
index 0000000..cedd07c
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/test/java/org/example/AccountApplicationTest.java
@@ -0,0 +1,19 @@
+package org.example;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+/**
+ * 用户账户微服务环境可用性校验
+ * */
+@SpringBootTest
+@RunWith(SpringRunner.class)
+public class AccountApplicationTest {
+
+ @Test
+ public void contextLoad() {
+
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/test/java/org/example/service/AddressServiceTest.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/test/java/org/example/service/AddressServiceTest.java
new file mode 100644
index 0000000..8cc7303
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/test/java/org/example/service/AddressServiceTest.java
@@ -0,0 +1,76 @@
+package org.example.service;
+
+import com.alibaba.fastjson2.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.example.account.AddressInfo;
+import org.example.common.TableId;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.Collections;
+
+/**
+ * 用户地址相关服务功能测试
+ * */
+@Slf4j
+public class AddressServiceTest extends BaseTest {
+
+ @Autowired
+ private IAddressService addressService;
+
+ /**
+ * 测试创建用户地址信息
+ * */
+ @Test
+ public void testCreateAddressInfo() {
+
+ AddressInfo.AddressItem addressItem = new AddressInfo.AddressItem();
+ addressItem.setUsername("qqq");
+ addressItem.setPhone("18800000001");
+ addressItem.setProvince("上海市");
+ addressItem.setCity("上海市");
+ addressItem.setAddressDetail("陆家嘴");
+
+ log.info("test create address info: [{}]", JSON.toJSONString(
+ addressService.createAddressInfo(
+ new AddressInfo(loginUserInfo.getId(),
+ Collections.singletonList(addressItem))
+ )
+ ));
+ }
+
+ /**
+ * 测试获取当前登录用户地址信息
+ * */
+ @Test
+ public void testGetCurrentAddressInfo() {
+
+ log.info("test get current user info: [{}]", JSON.toJSONString(
+ addressService.getCurrentAddressInfo()
+ ));
+ }
+
+ /**
+ * 测试通过 id 获取用户地址信息
+ * */
+ @Test
+ public void testGetAddressInfoById() {
+
+ log.info("test get address info by id: [{}]", JSON.toJSONString(
+ addressService.getAddressInfoById(1L)
+ ));
+ }
+
+ /**
+ * 测试通过 TableId 获取用户地址信息
+ * */
+ @Test
+ public void testGetAddressInfoByTableId() {
+
+ log.info("test get address info by table id: [{}]", JSON.toJSONString(
+ addressService.getAddressInfoByTableId(
+ new TableId(Collections.singletonList(new TableId.Id(1L)))
+ )
+ ));
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/test/java/org/example/service/BalanceServiceTest.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/test/java/org/example/service/BalanceServiceTest.java
new file mode 100644
index 0000000..9b68802
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/test/java/org/example/service/BalanceServiceTest.java
@@ -0,0 +1,43 @@
+package org.example.service;
+
+import com.alibaba.fastjson2.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.example.account.BalanceInfo;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 用于余额相关服务测试
+ * */
+@Slf4j
+public class BalanceServiceTest extends BaseTest {
+
+ @Autowired
+ private IBalanceService balanceService;
+
+ /**
+ * 测试获取当前用户的余额信息
+ * */
+ @Test
+ public void testGetCurrentUserBalanceInfo() {
+
+ log.info("test get current user balance info: [{}]", JSON.toJSONString(
+ balanceService.getCurrentUserBalanceInfo()
+ ));
+ }
+
+ /**
+ * 测试扣减用于余额
+ * */
+ @Test
+ public void testDeductBalance() {
+
+ BalanceInfo balanceInfo = new BalanceInfo();
+ balanceInfo.setUserId(loginUserInfo.getId());
+ balanceInfo.setBalance(1000L);
+
+ log.info("test deduct balance: [{}]", JSON.toJSONString(
+ balanceService.deductBalance(balanceInfo)
+ ));
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/test/java/org/example/service/BaseTest.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/test/java/org/example/service/BaseTest.java
new file mode 100644
index 0000000..bce73dc
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/test/java/org/example/service/BaseTest.java
@@ -0,0 +1,31 @@
+package org.example.service;
+
+import org.example.filter.AccessContext;
+import org.example.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;
+
+/**
+ * 测试用例基类, 填充登录用户信息
+ * */
+@SpringBootTest
+@RunWith(SpringRunner.class)
+public abstract class BaseTest {
+
+ protected final LoginUserInfo loginUserInfo = new LoginUserInfo(
+ 10L, "q@bbbbbbyyyyyy.com"
+ );
+
+ @Before
+ public void init() {
+ AccessContext.setLoginUserInfo(loginUserInfo);
+ }
+
+ @After
+ public void destroy() {
+ AccessContext.clearLoginUserInfo();
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/用户账户微服务.md b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/用户账户微服务.md
new file mode 100644
index 0000000..82f8ae5
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/用户账户微服务.md
@@ -0,0 +1,80 @@
+## 用户账户微服务
+
+### 用户身份登录统一拦截
+- 所有需要开发的微服务通用的配置
+
+### 集成 Swagger2 实现代码即文档
+-
+
+### 用户账户微服务功能设计
+-
+
+### 数据表及 ORM 过程
+-
+
+### 用户地址与余额服务接口定义
+- Service 返回包装成类进行返回, 尽量不要仅仅只返回一个ID, 防止后续的扩展需求
+- 不要设计通过请求参数来获取当前用户的余额信息, 因为可以通过请求劫持来拿到别的请求信息, 防止越权
+### 用户地址相关服务接口实现
+
+
+### 用户地址服务接口可用性测试(测试用例)
+
+
+### 用户余额相关服务接口实现
+
+
+### 用户余额服务接口可用性测试(测试用例)
+
+
+### 用户账户微服务对外 HTTP 接口
+
+
+### 验证用户账户微服务功能可用性
+```json
+[
+ {
+ "id": "dev-protocol-springcloud-project-account-service",
+ "order": 0, // order 越小优先级越高0
+ "predicates": [
+ {
+ "args": {
+ "pattern": "/dev-protocol-springcloud-gateway/dev-protocol-springcloud-project-account-service/**"
+ },
+ "name": "Path"
+ }
+ ],
+ "uri": "lb://dev-protocol-springcloud-project-account-service",
+ "filters": [
+ {
+ "name": "HeaderToken"
+ },
+ {
+ "name": "StripPrefix",
+ "args": {
+ "parts": "1" // 用来跳过上面的 pattern 前面的配置, 因为 [bootstrap.yml] 中的配置 context-path
+ }
+ }
+ ]
+ }
+]
+```
+- 补充放在nacos 的 gateway 的配置文件中
+### 用户账户微服务总结
+
+- 微服务开始之前的准备工作
+ - 用户身份登录拦截
+ - 在请求进入 service 之前解析 header 中的 token 信息, 并填充用户信息到上下文中
+ - 在请求结束之后,清理掉上下文中的用户信息
+ - 对于一些特定的 HTTP 请求不要拦截(即白名单 )
+ - 代码即文档:引入 Swagger
+ - pom 中添加依赖配置
+ - 自定义配置 Swagger
+---
+- 微服务模块的设计思想
+ - 微服务模块应该是低耦合、尽可能多的重用代码
+ - Tips:设计并不唯-!
+ - ![用户账户微服务总设计.png](pic/用户账户微服务总设计.png)
+---
+- 用户账户微服务的功能及在业务中的位置
+ - ![账户微服务在业务中的位置.png](pic/账户微服务在业务中的位置.png)
\ No newline at end of file
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/电商-授权鉴权服务.md b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/电商-授权鉴权服务.md
index 46e57b2..61acd2e 100644
--- a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/电商-授权鉴权服务.md
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/电商-授权鉴权服务.md
@@ -33,5 +33,26 @@
- http 脚本验证 对外接口是否可用
## 授权、鉴权中心微服务总结
+- 对比基于 Token 与基于服务器的身份认证
+ - 最为传统的做法,客户端存储 Cookie(一般是 Sessionid),服务器存储 Session
+ - Session 是每次用户认证通过以后,服务器需要创建一条记录保存用户信息,通常是在内存中,随着认证通过的用户越来越多,服务器的在这里的开销就会越来越大
+ - 在不同域名之前切换时,请求可能会被禁止;即跨域问题
+---
+- 基于 Token(JWT)的身份认证
+ - JWT 与 Session的差异相同点是,它们都是存储用户信息;然而,Session是在服务器端的,而JWT是在客户端的
+ - JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力
+---
+- 对比基于 Token 与基于服务器的身份认证
+ - 两者优缺点的对比
+ - 解析方法: JWT 使用算法直接解析得到用户信息; Session 需要额外的数据映射实现匹配
+ - 管理方法: JWT只有过期时间的限制:Session 数据保存在服务器,可控性更强
+ - 跨平台: JWT就是一段字符串,可以任意传播;Session 跨平台需要有统一的解析平台,较为繁琐
+ - 时效性: JWT 一旦生成,独立存在,很难做特殊控制:Session 时效性完全由服务端的逻辑说了算
+ - Tips:各自都有优缺点, 都是登录, 授权的解决方案
+---
- 生成数据库 文档
- - [DBDocTest.java]
\ No newline at end of file
+ - [DBDocTest.java]
+
+
+
+
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/pom.xml b/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/pom.xml
index 225a19c..f726566 100644
--- a/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/pom.xml
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/pom.xml
@@ -28,6 +28,10 @@
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-starter-test
+
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/README.md b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/README.md
new file mode 100644
index 0000000..1ad77da
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/README.md
@@ -0,0 +1,6 @@
+# 介绍
+- 正常服务的通用配置
+- 开发别的模块也要直接进行导入
+
+# 已实现功能
+- 统一身份拦截器
\ No newline at end of file
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/pom.xml b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/pom.xml
new file mode 100644
index 0000000..0f30289
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/pom.xml
@@ -0,0 +1,51 @@
+
+
+ 4.0.0
+
+ org.example
+ dev-protocol
+ 1.0-SNAPSHOT
+ ../../pom.xml
+
+
+ dev-protocol-springcloud-project-service-config
+
+
+ dev-protocol-springcloud-project-service-config
+ 服务通用配置
+
+
+ 8
+ 8
+ UTF-8
+
+
+
+
+ org.example
+ dev-protocol-springcloud-project-mvc-config
+ 1.0-SNAPSHOT
+
+
+
+
+ io.springfox
+ springfox-swagger2
+ 2.9.2
+
+
+ io.springfox
+ springfox-swagger-ui
+ 2.9.2
+
+
+
+ com.github.xiaoymin
+ swagger-bootstrap-ui
+ 1.9.3
+
+
+
+
\ No newline at end of file
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/conf/DevProtocolWebMvcConfig.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/conf/DevProtocolWebMvcConfig.java
new file mode 100644
index 0000000..b78b70d
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/conf/DevProtocolWebMvcConfig.java
@@ -0,0 +1,44 @@
+package org.example.conf;
+
+import org.example.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;
+
+/**
+ * Web Mvc 配置
+ * */
+@Configuration
+@SuppressWarnings("all")
+public class DevProtocolWebMvcConfig 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);
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/conf/SwaggerConfig.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/conf/SwaggerConfig.java
new file mode 100644
index 0000000..6062f12
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/conf/SwaggerConfig.java
@@ -0,0 +1,56 @@
+package org.example.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;
+
+/**
+ * Swagger 配置类
+ * 原生: /swagger-ui.html
+ * 美化: /doc.html
+ * */
+@Configuration
+@EnableSwagger2
+@SuppressWarnings("all")
+public class SwaggerConfig {
+
+ /**
+ * Swagger 实例 Bean 是 Docket, 所以通过配置 Docket 实例来配置 Swagger
+ * */
+ @Bean
+ public Docket docket() {
+
+ return new Docket(DocumentationType.SWAGGER_2)
+ // 展示在 Swagger 页面上的自定义工程描述信息
+ .apiInfo(apiInfo())
+ // 选择展示哪些接口
+ .select()
+ // 只有 org.example 包内的才去展示
+ // fixme 这个可以后续进行把一些不展示的接口进行规避
+ .apis(RequestHandlerSelectors.basePackage("org.example"))
+ .paths(PathSelectors.any())
+ .build();
+ }
+
+ /**
+ * Swagger 的描述信息
+ * */
+ public ApiInfo apiInfo() {
+
+ return new ApiInfoBuilder()
+ .title("dev-protocol-micro-service")
+ .description("dev-protocol-springcloud-project")
+ .contact(new Contact(
+ "q", "www.q.com", "q@by.com"
+ ))
+ .version("1.0")
+ .build();
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/filter/AccessContext.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/filter/AccessContext.java
new file mode 100644
index 0000000..9595eec
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/filter/AccessContext.java
@@ -0,0 +1,25 @@
+package org.example.filter;
+
+import org.example.vo.LoginUserInfo;
+
+/**
+ * 使用 ThreadLocal 去单独存储每一个线程携带的 LoginUserInfo 信息
+ * 要及时的清理我们保存到 ThreadLocal 中的用户信息:
+ * 1. 保证没有资源泄露
+ * 2. 保证线程在重用时, 不会出现数据混乱
+ * */
+public class AccessContext {
+ private static final ThreadLocal loginUserInfo = new ThreadLocal<>();
+
+ public static LoginUserInfo getLoginUserInfo() {
+ return loginUserInfo.get();
+ }
+
+ public static void setLoginUserInfo(LoginUserInfo loginUserInfo_) {
+ loginUserInfo.set(loginUserInfo_);
+ }
+
+ public static void clearLoginUserInfo() {
+ loginUserInfo.remove();
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/filter/LoginUserInfoInterceptor.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/filter/LoginUserInfoInterceptor.java
new file mode 100644
index 0000000..071b216
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/filter/LoginUserInfoInterceptor.java
@@ -0,0 +1,84 @@
+package org.example.filter;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.example.constant.CommonConstant;
+import org.example.utils.TokenParseUtils;
+import org.example.vo.LoginUserInfo;
+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;
+
+/**
+ * 用户身份统一登录拦截
+ * */
+@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 = TokenParseUtils.parseUserInfoFromToken(token);
+ } catch (Exception ex) {
+ log.error("parse login user info error: [{}]", ex.getMessage(), ex);
+ }
+
+ // 如果程序走到这里, 说明 header 中没有 token 信息
+ if (null == loginUserInfo) {
+ throw new RuntimeException("can not parse current login user");
+ }
+
+ log.info("set login user info: [{}]", request.getRequestURI());
+ // 设置当前请求上下文, 把用户信息填充进去
+ AccessContext.setLoginUserInfo(loginUserInfo);
+
+ return true;
+ }
+
+ @Override
+ public void postHandle(HttpServletRequest request, HttpServletResponse response,
+ Object handler, ModelAndView modelAndView) throws Exception {
+
+ }
+
+ /**
+ * 在请求完全结束后调用, 常用于清理资源等工作
+ * */
+ @Override
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
+ Object handler, Exception ex) throws Exception {
+
+ if (null != AccessContext.getLoginUserInfo()) {
+ AccessContext.clearLoginUserInfo();
+ }
+ }
+
+ /**
+ * 校验是否是白名单接口
+ * swagger2 接口
+ * */
+ private boolean checkWhiteListUrl(String url) {
+
+ return StringUtils.containsAny(
+ url,
+ "springfox", "swagger", "v2",
+ "webjars", "doc.html"
+ );
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/package-info.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/package-info.java
new file mode 100644
index 0000000..3035e19
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/package-info.java
@@ -0,0 +1 @@
+package org.example;
\ No newline at end of file
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/README.md b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/README.md
new file mode 100644
index 0000000..a6bf9dd
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/README.md
@@ -0,0 +1,6 @@
+# 介绍
+- 供数据传输的数据结构, 以及转换方式
+- 开发别的模块也要直接进行导入
+
+# 已加入实体
+- 账号微服务 sdk
\ No newline at end of file
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/pom.xml b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/pom.xml
new file mode 100644
index 0000000..d4535ff
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/pom.xml
@@ -0,0 +1,52 @@
+
+
+ 4.0.0
+
+ org.example
+ dev-protocol
+ 1.0-SNAPSHOT
+ ../../pom.xml
+
+
+ dev-protocol-springcloud-project-service-sdk
+ jar
+
+
+ dev-protocol-springcloud-project-service-sdk
+ 服务模块 SDK
+
+
+ 8
+ 8
+ UTF-8
+
+
+
+
+
+ io.springfox
+ springfox-swagger2
+ 2.9.2
+
+
+ io.springfox
+ springfox-swagger-ui
+ 2.9.2
+
+
+
+ com.github.xiaoymin
+ swagger-bootstrap-ui
+ 1.9.3
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
\ No newline at end of file
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/account/AddressInfo.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/account/AddressInfo.java
new file mode 100644
index 0000000..5a63aa7
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/account/AddressInfo.java
@@ -0,0 +1,80 @@
+package org.example.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;
+
+/**
+ * 用户地址信息
+ * */
+@ApiModel(description = "用户地址信息")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class AddressInfo {
+
+ @ApiModelProperty(value = "地址所属用户 id")
+ private Long userId;
+
+ @ApiModelProperty(value = "地址详细信息")
+ private List 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;
+ }
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/account/BalanceInfo.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/account/BalanceInfo.java
new file mode 100644
index 0000000..f13cca1
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/account/BalanceInfo.java
@@ -0,0 +1,23 @@
+package org.example.account;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 用户账户余额信息
+ * */
+@ApiModel(description = "用户账户余额信息")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class BalanceInfo {
+
+ @ApiModelProperty(value = "用户主键 id")
+ private Long userId;
+
+ @ApiModelProperty(value = "用户账户余额")
+ private Long balance;
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/account/UserAddress.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/account/UserAddress.java
new file mode 100644
index 0000000..38ec11e
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/account/UserAddress.java
@@ -0,0 +1,32 @@
+package org.example.account;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 用户地址信息
+ * */
+@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;
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/common/TableId.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/common/TableId.java
new file mode 100644
index 0000000..44ddf80
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/common/TableId.java
@@ -0,0 +1,32 @@
+package org.example.common;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 主键 ids
+ * */
+@ApiModel(description = "通用 id 对象")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TableId {
+
+ @ApiModelProperty(value = "数据表记录主键")
+ private List ids;
+
+ @ApiModel(description = "数据表记录主键对象")
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class Id {
+
+ @ApiModelProperty(value = "数据表记录主键")
+ private Long id;
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/goods/DeductGoodsInventory.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/goods/DeductGoodsInventory.java
new file mode 100644
index 0000000..8591790
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/goods/DeductGoodsInventory.java
@@ -0,0 +1,23 @@
+package org.example.goods;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 扣减商品库存
+ * */
+@ApiModel(description = "扣减商品库存对象")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class DeductGoodsInventory {
+
+ @ApiModelProperty(value = "商品主键 id")
+ private Long goodsId;
+
+ @ApiModelProperty(value = "扣减个数")
+ private Integer count;
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/goods/GoodsInfo.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/goods/GoodsInfo.java
new file mode 100644
index 0000000..ae22694
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/goods/GoodsInfo.java
@@ -0,0 +1,80 @@
+package org.example.goods;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+/**
+ * 商品信息
+ * */
+@ApiModel(description = "详细的商品信息")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class GoodsInfo {
+
+ @ApiModelProperty(value = "商品表主键 id")
+ private Long id;
+
+ @ApiModelProperty(value = "商品类别编码")
+ private String goodsCategory;
+
+ @ApiModelProperty(value = "品牌分类编码")
+ private String brandCategory;
+
+ @ApiModelProperty(value = "商品名称")
+ private String goodsName;
+
+ @ApiModelProperty(value = "商品图片")
+ private String goodsPic;
+
+ @ApiModelProperty(value = "商品描述信息")
+ private String goodsDescription;
+
+ @ApiModelProperty(value = "商品状态")
+ private Integer goodsStatus;
+
+ @ApiModelProperty(value = "商品价格, 单位: 分")
+ private Integer price;
+
+ @ApiModelProperty(value = "商品属性")
+ private GoodsProperty goodsProperty;
+
+ @ApiModelProperty(value = "供应量")
+ private Long supply;
+
+ @ApiModelProperty(value = "库存")
+ private Long inventory;
+
+ @ApiModelProperty(value = "创建时间")
+ private Date createTime;
+
+ @ApiModelProperty(value = "更新时间")
+ private Date updateTime;
+
+ /**
+ * 商品属性
+ * */
+ @ApiModel(description = "商品属性对象")
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class GoodsProperty {
+
+ @ApiModelProperty(value = "尺寸")
+ private String size;
+
+ @ApiModelProperty(value = "颜色")
+ private String color;
+
+ @ApiModelProperty(value = "材质")
+ private String material;
+
+ @ApiModelProperty(value = "图案")
+ private String pattern;
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/goods/SimpleGoodsInfo.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/goods/SimpleGoodsInfo.java
new file mode 100644
index 0000000..4a84b84
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/goods/SimpleGoodsInfo.java
@@ -0,0 +1,33 @@
+package org.example.goods;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 简单的商品信息: 封面
+ * */
+@ApiModel(description = "简单的商品信息")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class SimpleGoodsInfo {
+
+ @ApiModelProperty(value = "商品表主键 id")
+ private Long id;
+
+ @ApiModelProperty(value = "商品名称")
+ private String goodsName;
+
+ @ApiModelProperty(value = "商品图片")
+ private String goodsPic;
+
+ @ApiModelProperty(value = "商品价格, 单位: 分")
+ private Integer price;
+
+ public SimpleGoodsInfo(Long id) {
+ this.id = id;
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/package-info.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/package-info.java
new file mode 100644
index 0000000..3035e19
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/package-info.java
@@ -0,0 +1 @@
+package org.example;
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 076a458..da3e3d8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,6 +57,9 @@
dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center
dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config
dev-protocol-springcloud/dev-protocol-springcloud-project-common
+ dev-protocol-springcloud/dev-protocol-springcloud-project-account-service
+ dev-protocol-springcloud/dev-protocol-springcloud-project-service-config
+ dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk