changgou_gateway
网关模块,根据网站的规模和需要,可以将综合逻辑相关的服务用网关路由组合到一起。在这里还可以做鉴权和限流相关操作。
changgou_service
微服务模块,该模块用于存放所有独立的微服务工程。
changgou_service_api
对应工程的JavaBean、Feign、以及Hystrix配置,该工程主要对外提供依赖。
changgou_transaction_fescar
分布式事务模块,将分布式事务抽取到该工程中,任何工程如需要使用分布式事务,只需依赖该工程即可。
changgou_web
web服务工程,对应功能模块如需要调用多个微服务,可以将他们写入到该模块中,例如网站后台、网站前台等
从上边可以看出来,有很多微服务项目,所有我们需要为这些微服务搭建一个父工程;创建父工程 changgou_parent ,pom.xml文件中增加配置
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.changgougroupId>
<artifactId>changgou_parentartifactId>
<packaging>pompackaging>
<version>1.0-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.4.RELEASEversion>
parent>
<properties>
<skipTests>trueskipTests>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Greenwich.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
project>
删除父工程里的src文件夹(因为是父工程,不写代码,只提供依赖,所以删除src目录)
创建changgou_gateway、changgou_service、changgou_service_api、changgou_web工程,工程全部为pom工程,同样,将所有工程的src文件删除。
创建模块changgou_eureka
<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>changgou_parentartifactId>
<groupId>com.changgougroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>changgou_eurekaartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
project>
server:
port: 6868
eureka:
client:
register-with-eureka: false #是否将自己注册到eureka中
fetch-registry: false #是否从eureka中获取信息
service-url:
defaultZone: http://127.0.0.1:${
server.port}/eureka/
创建包com.changgou.eureka 包下创建启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class);
}
}
<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>changgou_parentartifactId>
<groupId>com.changgougroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>changgou_commonartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.51version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
dependencies>
project>
公共子模块引入这些依赖后,其他微服务引入changgou_common后也自动引入了这些依赖
/**
* 返回结果实体类
*/
public class Result {
private boolean flag;//是否成功
private Integer code;//返回码
private String message;//返回消息
private Object data;//返回数据
public Result(boolean flag, Integer code, String message, Object data) {
this.flag = flag;
this.code = code;
this.message = message;
this.data = data;
}
public Result(boolean flag, Integer code, String message) {
this.flag = flag;
this.code = code;
this.message = message;
}
public Result() {
this.flag = true;
this.code = StatusCode.OK;
this.message = "执行成功";
}
// getter and setter.....
}
建立类用于承载分页的数据结果(该项目不使用这个分页,只做了解)
/**
* 分页结果类
*/
public class PageResult<T> {
private Long total;//总记录数
private List<T> rows;//记录
public PageResult(Long total, List<T> rows) {
this.total = total;
this.rows = rows;
}
public PageResult() {
}
//getter and setter ......
}
创建返回状态码实体类
/**
* 返回码
*/
public class StatusCode {
public static final int OK=20000;//成功
public static final int ERROR =20001;//失败
public static final int LOGINERROR =20002;//用户名或密码错误
public static final int ACCESSERROR =20003;//权限不足
public static final int REMOTEERROR =20004;//远程调用失败
public static final int REPERROR =20005;//重复操作
}
package com.changgou.util;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
/**
* 名称:IdWorker.java
* 描述:分布式自增长ID
*
* Twitter的 Snowflake JAVA实现方案 每秒可以生成26万个不重复的ID
*
* 核心代码为其IdWorker这个类实现,其原理结构如下,我分别用一个0表示一位,用—分割开部分的作用:
* 1||0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
* 在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,
* 然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),
* 然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。
* 这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),
* 并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。
*
* 64位ID (42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加))
*
* @author Polim
*/
public class IdWorker {
// 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
private final static long twepoch = 1288834974657L;
// 机器标识位数
private final static long workerIdBits = 5L;
// 数据中心标识位数
private final static long datacenterIdBits = 5L;
// 机器ID最大值
private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 数据中心ID最大值
private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 毫秒内自增位
private final static long sequenceBits = 12L;
// 机器ID偏左移12位
private final static long workerIdShift = sequenceBits;
// 数据中心ID左移17位
private final static long datacenterIdShift = sequenceBits + workerIdBits;
// 时间毫秒左移22位
private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
/* 上次生产id时间戳 */
private static long lastTimestamp = -1L;
// 0,并发控制
private long sequence = 0L;
private final long workerId;
// 数据标识id部分
private final long datacenterId;
public IdWorker(){
this.datacenterId = getDatacenterId(maxDatacenterId);
this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
}
/**
* @param workerId
* 工作机器ID
* @param datacenterId
* 序列号
*/
public IdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 获取下一个ID
*
* @return
*/
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
// 当前毫秒内,则+1
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 当前毫秒内计数满了,则等待下一秒
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// ID偏移组合生成最终的ID,并返回ID
long nextId = ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift) | sequence;
return nextId;
}
private long tilNextMillis(final long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
/**
*
* 获取 maxWorkerId
*
*/
protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
StringBuffer mpid = new StringBuffer();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (!name.isEmpty()) {
/*
* GET jvmPid
*/
mpid.append(name.split("@")[0]);
}
/*
* MAC + PID 的 hashcode 获取16个低位
*/
return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}
/**
*
* 数据标识id部分
*
*/
protected static long getDatacenterId(long maxDatacenterId) {
long id = 0L;
try {
InetAddress ip = InetAddress.getLocalHost();
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
if (network == null) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
id = ((0x000000FF & (long) mac[mac.length - 1])
| (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
id = id % (maxDatacenterId + 1);
}
} catch (Exception e) {
System.out.println(" getDatacenterId: " + e.getMessage());
}
return id;
}
public static void main(String[] args) {
IdWorker idWorker=new IdWorker(0,0);
for(int i=0;i<10000;i++){
long nextId = idWorker.nextId();
System.out.println(nextId);
}
}
}
创建公共模块changgou_common_db ,删除src文件夹,pom文件引入依赖
<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>changgou_parentartifactId>
<groupId>com.changgougroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>changgou_common_dbartifactId>
<dependencies>
<dependency>
<groupId>com.changgougroupId>
<artifactId>changgou_commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>tk.mybatisgroupId>
<artifactId>mapper-spring-boot-starterartifactId>
<version>2.0.4version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelper-spring-boot-starterartifactId>
<version>1.2.3version>
dependency>
dependencies>
project>
这个公共模块是连接mysql数据库的公共微服务模块,所以需要连接mysql的微服务都继承自此工程。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
<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>changgou_serviceartifactId>
<groupId>com.changgougroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>changgou_service_goodsartifactId>
<dependencies>
<dependency>
<groupId>com.changgougroupId>
<artifactId>changgou_common_dbartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.changgougroupId>
<artifactId>changgou_service_goods_apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
project>
server:
port: 9011
spring:
application:
name: goods
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.200.128:3306/changgou_goods?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: admin
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
#hystrix 配置
hystrix:
command:
default:
execution:
timeout:
#如果enabled设置为false,则请求超时交给ribbon控制
enabled: true
isolation:
strategy: SEMAPHORE
# 由于使用MyBatis通用Mapper,实现了0sql,所以这里不写MyBatis配置了
@SpringBootApplication
//开启Eureka客户端
@EnableEurekaClient
//开启通用Mapper包扫描,注意导入的包是tk下的包
@MapperScan(basePackages = {
"com.changgou.goods.dao"})
public class GoodsApplication {
public static void main(String[] args) {
SpringApplication.run(GoodsApplication.class);
}
}
创建商品微服务,实现对品牌表的增删改查功能。具体包括
(1)查询全部列表数据
(2)根据ID查询实体数据
(3)增加
(4)修改
(5)删除
(6)条件查询
(7)分页查询
(8)分页+条件查询
(9)公共异常处理
字段名称 | 字段含义 | 字段类型 | 字段长度 | 备注 |
---|---|---|---|---|
id | 品牌id | INT | ||
name | 品牌名称 | VARCHAR | ||
image | 品牌图片地址 | VARCHAR | ||
letter | 品牌的首字母 | CHAR | ||
seq | 排序 | INT |
@Table(name="tb_brand")
public class Brand implements Serializable{
@Id
private Integer id;//品牌id
private String name;//品牌名称
private String image;//品牌图片地址
private String letter;//品牌的首字母
private Integer seq;//排序
// getter and setter .....(省略)
}
@Table和@Id都是JPA注解,@Table用于配置表与实体类的映射关系,@Id用于标识主键属性。
public interface BrandMapper extends Mapper<Brand> {
}
使用通用Mapper,不需要写sql语句;继承了Mapper接口,就自动实现了增删改查的常用方法,注意,Mapper接口导入的包是tk下的包(记的在启动类上加@MapperScan注解)
public interface BrandService {
/***
* 查询所有品牌
* @return
*/
public List<Brand> findAll();
}
创建com.changgou.goods.service.impl包,包下创建服务实现类 BrandServiceImpl,代码如下:
@Service
public class BrandServiceImpl implements BrandService {
@Autowired
private BrandMapper brandMapper;
@Override
public List<Brand> findAll() {
return brandMapper.selectAll();
}
}
控制层 com.changgou.goods包下创建controller包 ,包下创建类
@RestController
@RequestMapping("/brand")
/**
*跨域:A域名访问B域名的数据
* 域名或者请求端口或者协议不一致的时候,就是跨域了
* 域名不一致:www.baidu.com下的html,访问www.souhu.com下的任何资源
* 协议不一样:https://www.baidu.com 访问 http://www.baidu.com
* 端口不一样:www.baidu.com:8081 访问 www.baidu.com:8082
* 如果不加这个注解的话,跨域是访问不了的
**/
@CrossOrigin
public class BrandController {
@Autowired
private BrandService brandService;
@GetMapping
public Result findAll(){
List<Brand> brandList = brandService.findAll();
return new Result(true, StatusCode.OK,"查询成功",brandList) ;
}
}
修改com.changgou.goods.service.BrandService接口,添加根据ID查询品牌数据方法,代码如下:
/**
* 根据ID查询
* @param id
* @return
*/
public Brand findById(Integer id);
/**
* 根据ID查询
* @param id
* @return
*/
@Override
public Brand findById(Integer id){
return brandMapper.selectByPrimaryKey(id);
}
/***
* 根据ID查询品牌数据
* @param id
* @return
*/
@GetMapping("/{id}")
public Result findById(@PathVariable Integer id){
Brand brand = brandService.findById(id);
return new Result(true,StatusCode.OK,"查询成功",brand);
}
/***
* 新增品牌
* @param brand
*/
public void add(Brand brand);
/**
* 增加
* @param brand
*/
@Override
public void add(Brand brand){
//注意:使用insertSelective方法,不使用insert方法,因为insertSelective会忽略实体类里的空值属性(哪些属性有值就插入哪些属性值),而insert会将没有值的属性置为null后插入数据库里
brandMapper.insertSelective(brand);
}
/***
* 新增品牌数据
* @param brand
* @return
*/
@PostMapping
public Result add(@RequestBody Brand brand){
brandService.add(brand);
return new Result(true,StatusCode.OK,"添加成功");
}
/***
* 修改品牌数据
* @param brand
*/
public void update(Brand brand);
/**
* 修改
* @param brand
*/
@Override
public void update(Brand brand){
brandMapper.updateByPrimaryKeySelective(brand);
}
/***
* 修改品牌数据
* @param brand
* @param id
* @return
*/
@PutMapping(value="/{id}")
public Result update(@RequestBody Brand brand,@PathVariable Integer id){
brand.setId(id);
brandService.update(brand);
return new Result(true,StatusCode.OK,"修改成功");
}
/***
* 删除品牌
* @param id
*/
public void delete(Integer id);
/**
* 删除
* @param id
*/
@Override
public void delete(Integer id){
brandMapper.deleteByPrimaryKey(id);
}
/***
* 根据ID删除品牌数据
* @param id
* @return
*/
@DeleteMapping(value = "/{id}" )
public Result delete(@PathVariable Integer id){
brandService.delete(id);
return new Result(true,StatusCode.OK,"删除成功");
}
/***
* 多条件搜索品牌方法
* @param searchMap
* @return
*/
public List<Brand> findList(Map<String, Object> searchMap);
/**
* 条件查询
* @param searchMap
* @return
*/
@Override
public List<Brand> findList(Map<String, Object> searchMap){
Example example = createExample(searchMap);
return brandMapper.selectByExample(example);
}
/**
* 构建查询对象
* @param searchMap
* @return
*/
private Example createExample(Map<String, Object> searchMap){
Example example=new Example(Brand.class);
//条件构造器
Example.Criteria criteria = example.createCriteria();
if(searchMap!=null){
// 品牌名称
if(searchMap.get("name")!=null && !"".equals(searchMap.get("name"))){
criteria.andLike("name","%"+searchMap.get("name")+"%");
}
// 品牌的首字母
if(searchMap.get("letter")!=null && !"".equals(searchMap.get("letter"))){
criteria.andEqualTo("letter",searchMap.get("letter"));
}
}
return example;
}
/***
* 多条件搜索品牌数据
* @param searchMap
* @return
*/
@GetMapping(value = "/search" )
public Result findList(@RequestParam Map searchMap){
List<Brand> list = brandService.findList(searchMap);
return new Result(true,StatusCode.OK,"查询成功",list);
}
/***
* 分页查询
* @param page 当前页
* @param size 每页显示的条数
* @return
*/
public PageInfo<Brand> findPage(int page, int size);
@Override
public PageInfo<Brand> findPage(int page, int size){
PageHelper.startPage(page,size);
//由于上边使用了PageHelper分页,所以下边必须是一个集合类型的数
List<Brand> brands = brandMapper.selectAll();
return new PageInfo<Brand>(brands);
}
/***
* 分页搜索实现
* @param searchMap
* @param page
* @param size
* @return
*/
@GetMapping(value = "/search/{page}/{size}" )
public Result findPage(@PathVariable int page, @PathVariable int size){
PageInfo<Brand> pageList = brandService.findPage( page, size);
return new Result<PageInfo<Brand>>(true,StatusCode.OK,"分页查询成功",pageList);
}
其中,PageResult如下
package com.changgou.entity;
import java.util.List;
public class PageResult<T> {
private Long total;//总记录数
private List<T> rows;//记录
public PageResult(Long total, List<T> rows) {
this.total = total;
this.rows = rows;
}
public PageResult() {
}
public Long getTotal() {
return total;
}
public void setTotal(Long total) {
this.total = total;
}
public List<T> getRows() {
return rows;
}
public void setRows(List<T> rows) {
this.rows = rows;
}
}
/***
* 多条件分页查询
* @param searchMap
* @param page
* @param size
* @return
*/
Page<Brand> findPage(Map<String, Object> searchMap, int page, int size);
/**
* 条件+分页查询
* @param searchMap 查询条件
* @param page 页码
* @param size 页大小
* @return 分页结果
*/
@Override
public Page<Brand> findPage(Map<String,Object> searchMap, int page, int size){
PageHelper.startPage(page,size);
Example example = createExample(searchMap);
return (Page<Brand>)brandMapper.selectByExample(example);
}
/***
* 分页搜索实现
* @param searchMap
* @param page
* @param size
* @return
*/
@PostMapping(value = "/search/{page}/{size}" )
public Result findPage(@RequestParam Map searchMap, @PathVariable int page, @PathVariable int size){
Page<Brand> pageList = brandService.findPage(searchMap, page, size);
PageResult pageResult=new PageResult(pageList.getTotal(),pageList.getResult());
return new Result(true,StatusCode.OK,"查询成功",pageResult);
}
为了使我们的代码更容易维护,我们创建一个类集中处理异常
在com.changgou.goods.controller包下创建公共异常处理类BaseExceptionHandler
/**
* 统一异常处理类
*/
@ControllerAdvice
public class BaseExceptionHandler {
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result error( Exception e) {
e.printStackTrace();
return new Result(false, StatusCode.ERROR, e.getMessage());
}
}
此时,controller层报错的时候,就会返回json格式的错误信息数据给前台;