1. day01 项目搭建
2. 前后端分离
3. 技术架构
4. 系统架构
5. 项目分层
changgou_gateway
网关模块,根据网站的规模和需要,可以将综合逻辑相关的服务用网关路由组合到一起。在这里还可以做鉴权和限流相关操作。
changgou_service
微服务模块,该模块用于存放所有独立的微服务工程。
changgou_service_api
对应工程的JavaBean、Feign、以及Hystrix配置,该工程主要对外提供依赖。
changgou_transaction_fescar
分布式事务模块,将分布式事务抽取到该工程中,任何工程如需要使用分布式事务,只需依赖该工程即可。
changgou_web
web服务工程,对应功能模块如需要调用多个微服务,可以将他们写入到该模块中,例如网站后台、网站前台等
6. 项目搭建
6.1. 一级父工程搭建
创建父工程 changgou_parent 修改pom文件 并删除src目录
此项目工程为父工程 无需编写编码
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<properties>
<skipTests>true</skipTests>
</properties>
<!--依赖包-->
<dependencies>
<!--测试包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
6.2. 创建二级父工程项目
依次创建各个二级父工程 并删除项目中的src目录
- changgou_gateway
- changgou_service
- changgou_service_api
- changgou_transaction_fescar
- changgou_web
6.3. Eureka微服务搭建
创建二级父工程 changgou_eureka 并编辑pom
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
- 创建启动类并注解开启eurekasever
package com.changgou.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer //声明为eureka注册中心 服务端
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
- application 配置文件
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/
测试启动 运行启动类
6.4. 全局公共模块
创建子模块changgou_common pom引入模块
<dependencies>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- redis 使用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
创建常用对象 分页pojo 页面返回对象 返回结果对象 状态码
在com.changgou.common.pojo下创建
page
package com.changgou.common.pojo;
import java.io.Serializable;
import java.util.List;
/**
* 分页对象
* @param <T>
*/
public class Page <T> implements Serializable{
//当前默认为第一页
public static final Integer pageNum = 1;
//默认每页显示条件
public static final Integer pageSize = 20;
//判断当前页是否为空或是小于1
public static Integer cpn(Integer pageNum){
if(null == pageNum || pageNum < 1){
pageNum = 1;
}
return pageNum;
}
// 页数(第几页)
private long currentpage;
// 查询数据库里面对应的数据有多少条
private long total;// 从数据库查处的总记录数
// 每页查5条
private int size;
// 下页
private int next;
private List<T> list;
// 最后一页
private int last;
private int lpage;
private int rpage;
//从哪条开始查
private long start;
//全局偏移量
public int offsize = 2;
public Page() {
super();
}
/****
*
* @param currentpage
* @param total
* @param pagesize
*/
public void setCurrentpage(long currentpage,long total,long pagesize) {
//可以整除的情况下
long pagecount = total/pagesize;
//如果整除表示正好分N页,如果不能整除在N页的基础上+1页
int totalPages = (int) (total%pagesize==0? total/pagesize : (total/pagesize)+1);
//总页数
this.last = totalPages;
//判断当前页是否越界,如果越界,我们就查最后一页
if(currentpage>totalPages){
this.currentpage = totalPages;
}else{
this.currentpage=currentpage;
}
//计算start
this.start = (this.currentpage-1)*pagesize;
}
//上一页
public long getUpper() {
return currentpage>1? currentpage-1: currentpage;
}
//总共有多少页,即末页
public void setLast(int last) {
this.last = (int) (total%size==0? total/size : (total/size)+1);
}
/****
* 带有偏移量设置的分页
* @param total
* @param currentpage
* @param pagesize
* @param offsize
*/
public Page(long total,int currentpage,int pagesize,int offsize) {
this.offsize = offsize;
initPage(total, currentpage, pagesize);
}
/****
*
* @param total 总记录数
* @param currentpage 当前页
* @param pagesize 每页显示多少条
*/
public Page(long total,int currentpage,int pagesize) {
initPage(total,currentpage,pagesize);
}
/****
* 初始化分页
* @param total
* @param currentpage
* @param pagesize
*/
public void initPage(long total,int currentpage,int pagesize){
//总记录数
this.total = total;
//每页显示多少条
this.size=pagesize;
//计算当前页和数据库查询起始值以及总页数
setCurrentpage(currentpage, total, pagesize);
//分页计算
int leftcount =this.offsize, //需要向上一页执行多少次
rightcount =this.offsize;
//起点页
this.lpage =currentpage;
//结束页
this.rpage =currentpage;
//2点判断
this.lpage = currentpage-leftcount; //正常情况下的起点
this.rpage = currentpage+rightcount; //正常情况下的终点
//页差=总页数和结束页的差
int topdiv = this.last-rpage; //判断是否大于最大页数
/***
* 起点页
* 1、页差<0 起点页=起点页+页差值
* 2、页差>=0 起点和终点判断
*/
this.lpage=topdiv<0? this.lpage+topdiv:this.lpage;
/***
* 结束页
* 1、起点页<=0 结束页=|起点页|+1
* 2、起点页>0 结束页
*/
this.rpage=this.lpage<=0? this.rpage+(this.lpage*-1)+1: this.rpage;
/***
* 当起点页<=0 让起点页为第一页
* 否则不管
*/
this.lpage=this.lpage<=0? 1:this.lpage;
/***
* 如果结束页>总页数 结束页=总页数
* 否则不管
*/
this.rpage=this.rpage>last? this.last:this.rpage;
}
public long getNext() {
return currentpage<last? currentpage+1: last;
}
public void setNext(int next) {
this.next = next;
}
public long getCurrentpage() {
return currentpage;
}
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
public long getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public long getLast() {
return last;
}
public long getLpage() {
return lpage;
}
public void setLpage(int lpage) {
this.lpage = lpage;
}
public long getRpage() {
return rpage;
}
public void setRpage(int rpage) {
this.rpage = rpage;
}
public long getStart() {
return start;
}
public void setStart(long start) {
this.start = start;
}
public void setCurrentpage(long currentpage) {
this.currentpage = currentpage;
}
/**
* @return the list
*/
public List<T> getList() {
return list;
}
/**
* @param list the list to set
*/
public void setList(List<T> list) {
this.list = list;
}
public static void main(String[] args) {
//总记录数
//当前页
//每页显示多少条
int cpage =17;
Page page = new Page(1001,cpage,50,7);
System.out.println("开始页:"+page.getLpage()+"__当前页:"+page.getCurrentpage()+"__结束页"+page.getRpage()+"____总页数:"+page.getLast());
}
}
PageResult
package com.changgou.common.pojo;
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;
}
}
Result
package com.changgou.common.pojo;
/**
* 返回结果实体类
*/
public class Result<T> {
private boolean flag;//是否成功
private Integer code;//返回码
private String message;//返回消息
private T data;//返回数据
public Result(boolean flag, Integer code, String message, Object data) {
this.flag = flag;
this.code = code;
this.message = message;
this.data = (T)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 = "执行成功";
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
StatusCode
package com.changgou.common.pojo;
/**
* 返回码
*/
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;//重复操作
}
6.5. 数据访问公共模块搭建
这个公共模块是连接mysql数据库的公共微服务模块,所以需要连接mysql的微服务都继承自此工程。
创建公共模块changgou_common_db,pom文件引入依赖
<dependencies>
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--通用mapper起步依赖-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>
<!--MySQL数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
6.6. 商品微服务API工程搭建
创建 changgou_service_api 模块
<dependencies>
<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>
</dependencies>
6.6.1. 在api工程下创建goods_api子模块
changgou_service_api 下创建changgou_service_goods_api子模块并添加common依赖
<dependencies>
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
6.7. 微服务工程搭建
changgou_service下创建changgou_service_goods子模块
<dependencies>
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou_common_db</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou_service_goods_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
application.yml
server:
port: 9011
spring:
application:
name: goods
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.130.128:3306/changgou_goods?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
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
创建启动类 并声明为eureka客户端 并开启mapper扫描
package com.changgou.service.goods;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@EnableEurekaClient //声明为eureka客户端
@MapperScan(basePackages = {"com.changgou.service.goods.dao"}) //扫描指定路径下的dao层接口
public class GoodsApplication {
public static void main(String[] args) {
SpringApplication.run(GoodsApplication.class, args);
}
}
7. 增删改查
- 在changou_service_api中的changou_service_goods_api 用于放置 pojo
创建品牌的映射类
package com.changgou.goods.pojo;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@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;//排序
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public String getLetter() {
return letter;
}
public void setLetter(String letter) {
this.letter = letter;
}
public Integer getSeq() {
return seq;
}
public void setSeq(Integer seq) {
this.seq = seq;
}
}
- 在changogu_service中的changogu_service_goods 创建 dao层并继承Mapper序列化 泛型为映射类
package com.changgou.service.goods.dao;
import com.changgou.goods.pojo.Brand;
import tk.mybatis.mapper.common.Mapper;
public interface BrandMapper extends Mapper<Brand> {
}
- 创建service 层 接口 调用dao层
package com.changgou.service.goods.service;
import com.changgou.goods.pojo.Brand;
import com.github.pagehelper.Page;
import java.util.List;
import java.util.Map;
public interface BrandService {
//品牌列表查询
List<Brand> findList();
//根据id查询
Brand findById(Integer id);
//品牌新增
void add(Brand brand);
//品牌修改
void update(Brand brand);
//品牌删除
void delById(Integer id);
//品牌列表条件查询
List<Brand> list(Map<String,Object> searchMap);
//品牌列表分页查询
Page<Brand> findPage(int page,int size);
//品牌列表分页+条件查询
Page<Brand> findPage(Map<String,Object> searchMap,int page,int size);
}
- 创建service 层的实现类 impl
package com.changgou.service.goods.service.impl;
import com.changgou.goods.pojo.Brand;
import com.changgou.service.goods.dao.BrandMapper;
import com.changgou.service.goods.service.BrandService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;
import java.util.List;
import java.util.Map;
@Service
public class BrandServiceImpl implements BrandService {
@Autowired
private BrandMapper brandMapper;
//品牌列表查询
@Override
public List<Brand> findList() {
List<Brand> brandList = brandMapper.selectAll();
return brandList;
}
//根据id查询
@Override
public Brand findById(Integer id) {
Brand brand = brandMapper.selectByPrimaryKey(id);
return brand;
}
//添加品牌
@Override
public void add(Brand brand) {
brandMapper.insert(brand);
}
//更新品牌
@Override
public void update(Brand brand) {
brandMapper.updateByPrimaryKey(brand);
}
//根据ids删除
@Override
@Transactional
public void delById(Integer id) {
brandMapper.deleteByPrimaryKey(id);
}
//品牌列表条件查询
@Override
public List<Brand> list(Map<String, Object> searchMap) {
Example example = new Example(Brand.class);
//封装查询条件
Example.Criteria criteria = example.createCriteria();
if (searchMap != null) {
//品牌名称 like模糊查询
if (searchMap.get("name") != null && !"".equals(searchMap.get("name"))) {
//andLike like品牌 property属性名 value为查询条件
criteria.andLike("name", "%" + searchMap.get("name") + "%");
}
//按品牌首字母进行查询 精确查询
if (searchMap.get("letter") != null && !"".equals(searchMap.get("letter"))) {
criteria.andEqualTo("letter", searchMap.get("letter"));
}
}
List<Brand> brandList = brandMapper.selectByExample(example);
return brandList;
}
@Override
public Page<Brand> findPage(int page, int size) {
PageHelper.startPage(page, size);
Page<Brand> page1 = (Page<Brand>) brandMapper.selectAll();
return page1;
}
@Override
public Page<Brand> findPage(Map<String, Object> searchMap, int page, int size) {
//分页
PageHelper.startPage(page, size);
//设置查询条件
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"));
}
}
Page<Brand> pageInfo = (Page<Brand>) brandMapper.selectByExample(example);
return pageInfo;
}
}
- controller层
package com.changgou.service.goods.controller;
import com.changgou.common.pojo.PageResult;
import com.changgou.common.pojo.Result;
import com.changgou.common.pojo.StatusCode;
import com.changgou.goods.pojo.Brand;
import com.changgou.service.goods.service.BrandService;
import com.github.pagehelper.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RequestMapping("/brand")
@RestController
public class BrandController {
@Autowired
private BrandService brandService;
@GetMapping
public Result<List<Brand>> findList() {
List<Brand> brandList = brandService.findList();
return new Result<>(true, StatusCode.OK, "查询成功", brandList);
}
@GetMapping("/{id}")
public Result<Brand> findById(@PathVariable("id") Integer id) {
Brand brand = brandService.findById(id);
return new Result<>(true, StatusCode.OK, "查询成功", brand);
}
@PostMapping
private Result add(@RequestBody Brand brand) {
brandService.add(brand);
return new Result(true, StatusCode.OK, "添加成功");
}
@PutMapping("/{id}")
public Result update(@PathVariable("id") Integer id, @RequestBody Brand brand) {
brand.setId(id);
brandService.update(brand);
return new Result(true, StatusCode.OK, "更新成功");
}
@DeleteMapping("/{id}")
public Result delById(@PathVariable("id") Integer id) {
brandService.delById(id);
return new Result(true, StatusCode.OK, "删除成功");
}
@GetMapping("/search")
public Result<List<Brand>> search(@RequestParam Map searchMap) {
List<Brand> list = brandService.list(searchMap);
return new Result<>(true, StatusCode.OK, "查询成功", list);
}
@GetMapping("/search/{page}/{size}")
public Result findPage(
@PathVariable("page") int page,
@PathVariable("size") int size
) {
Page<Brand> pageInfo = brandService.findPage(page, size);
PageResult pageResult = new PageResult(pageInfo.getTotal(), pageInfo.getResult());
return new Result(true, StatusCode.OK, "查询成功", pageResult);
}
@GetMapping("/searchPage/{page}/{size}")
public Result findPage(@RequestParam Map searchMap, @PathVariable("page") int page,
@PathVariable("size") int size) {
Page pageInfo = brandService.findPage(searchMap, page, size);
PageResult pageResult = new PageResult(pageInfo.getTotal(), pageInfo.getResult());
return new Result(true, StatusCode.OK, "查询成功", pageResult);
}
}
8. 公共异常处理
上面的接口我们都是直接返回结果给前端 如果后端出现异常会直接返回java中异常错误结果
为了使我们代码更加容易维护 我们创建一个公共异常处理类 来进行处理
通过@ControllerAdvice 声明类为增强类
通过@ExceptionHandler(value = Exception.class) 捕抓指定的异常
package com.changgou.service.goods.handler;
import com.changgou.common.pojo.Result;
import com.changgou.common.pojo.StatusCode;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
//统一异常处理类
@ControllerAdvice //声明该类是一个增强类
public class BaseExceptionHandler {
@ExceptionHandler(value = Exception.class) //声明为异常处理的handler
@ResponseBody
public Result error(Exception e) {
e.printStackTrace();
return new Result(false, StatusCode.ERROR, "当前系统繁忙,请您稍后重试");
}
}
9. 跨域解决
出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
如果跨域调用,会出现如下错误:
No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:9100‘ is therefore not allowed access. The response had HTTP status code 400.
由于我们采用的是前后端分离的编程方式,前端和后端必定存在跨域问题。解决跨域问题可以采用CORS
9.1. CORS
CORS 是一个 W3C 标准,全称是”跨域资源共享”(Cross-origin resource sharing)。CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10。它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。
springMVC的版本在4.2或以上版本,可以使用注解实现跨域。 我们只需要在Controller类上添加注解@CrossOrigin 就可以了。
@RequestMapping("/brand")
@RestController
@CrossOrigin //开启跨域请求操作
public class BrandController {
}