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: 6868eureka: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;//判断当前页是否为空或是小于1public 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;}//计算startthis.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: 9011spring:application:name: goodsdatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.130.128:3306/changgou_goods?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTCusername: rootpassword: rooteureka:client:service-url:defaultZone: http://127.0.0.1:6868/eurekainstance:prefer-ip-address: truefeign:hystrix:enabled: true#hystrix 配置hystrix:command:default:execution:timeout:#如果enabled设置为false,则请求超时交给ribbon控制enabled: trueisolation: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 {@Idprivate Integer id;//品牌idprivate 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;@Servicepublic class BrandServiceImpl implements BrandService {@Autowiredprivate BrandMapper brandMapper;//品牌列表查询@Overridepublic List<Brand> findList() {List<Brand> brandList = brandMapper.selectAll();return brandList;}//根据id查询@Overridepublic Brand findById(Integer id) {Brand brand = brandMapper.selectByPrimaryKey(id);return brand;}//添加品牌@Overridepublic void add(Brand brand) {brandMapper.insert(brand);}//更新品牌@Overridepublic void update(Brand brand) {brandMapper.updateByPrimaryKey(brand);}//根据ids删除@Override@Transactionalpublic void delById(Integer id) {brandMapper.deleteByPrimaryKey(id);}//品牌列表条件查询@Overridepublic 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;}@Overridepublic Page<Brand> findPage(int page, int size) {PageHelper.startPage(page, size);Page<Brand> page1 = (Page<Brand>) brandMapper.selectAll();return page1;}@Overridepublic 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")@RestControllerpublic class BrandController {@Autowiredprivate BrandService brandService;@GetMappingpublic 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);}@PostMappingprivate 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@ResponseBodypublic 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 {}
