先下载tomcat,并解压到本地。
因为我电脑上有8080端口占用,所以首先先修改conf目录下的server.xml文件,将端口改为9090
修改完就可以启动了。。。。
直接双击bin目录下的startup.bat文件,服务器就跑起来了。
既然启动了服务器,那必然可以挂载前端项目到服务器上了!(后期会把这些静态资源挂载到nginx的)
需要前端资源可以私信作者
在我们平常的web项目中,通常都是localhost:port 或者 127.0.0.1:port访问,写的比较麻烦,在本地通过虚拟域名访问会清晰许多
下载一个switchhosts
下载地址:https://swh.app/zh/
首先看看自己的ip地址是多少吧
具体就是打开cmd输入“ipconfig”
将本地ip和你的接口或者访问路径连接。。
改完之后打开前端资源里的app.js,在这里有公用的请求资源,所以如果读者们像根据自己的需求修改端口,务必修改app.js里的数据
项目老师使用的MariaDB,但是我懒得下载,就直接用mysql了
需要安装mysql和图形化界面sqlyog,具体地址就不贴出来了。。。
然后就是sql文件了,想要的话私信我。。。
创建完成后,发现有6个表
admin_user,app_user,article,category,comments,fans
admin_user是主要用与存储管理员信息的表
这个表用于存储用户的表
这个表是用来存储网站文章的表
新闻资讯文章的分类
该表主要用于管理评论文章的用户,注意评论还可以被评论
该表主要用于管理粉丝
主要就是在idea中建立一个Module顶级工程,其他的基础工程都是在顶级工程中拓展的。。
首先在idea中,勾中maven项目创建一个顶级工程
之后就是导入pom依赖啦,由于篇幅,pom文件也不在这里贴出来了
我们的依赖主要导入了:springcloud,一些springboot的基本启动类,mongodb依赖,mysql驱动,mybatis,分页工具,okhttp(网络请求框架),jackson(用于json数据的传输),apache的工具类,google工具类,swagger依赖(接口文档),文件上传fastdfs,
1 聚合工程可以分为顶级项目(顶级工程,父工程)与子工程(子module模块),
这两者的关系其实就是父子继承的关系,子工程在maven中可以称之为module,
模块与模块之间是平级的,是可以相互依赖的。
2 子模块可以使用顶级工程中所有的资源(依赖),子模块之间如果有要使用资源的话,必须构建依赖
3 一个顶级工程是可以由多个不同的子工程共同组合而成。
注意
正如rabbitMq的rabbitmq和erlang一样,springcloud和springboot也有相对的版本对应,所以我们引入依赖的时候得导对。。
查看详情请前往spring的官网 : spring.io
该项目采用的依赖springboot的版本为2.2.5,所以自然需要Hoxton的springcloud
说到版本对应,我们所有以来的版本号都是放到properties中去管理的,properties管理非常方便,我们该版本就不需要一个一个找了,就比如我电脑的mysql版本是5.5.28,mysql驱动也得对应,就在properties中修改
看我们pom文件的最后一项dependencyManagement,这个标签有什么作用呢,因为这个工程是作为我们的顶级工程,其他工程都是在该工程下编写的,我们当然不需要我们原本的顶级工程也下载那么多jar包,这是没有意义的,刚才说了子工程可以继承父工程依赖,那么我们在子工程请求父工程依赖的时候,让他自己去外网下载,就非常好了,这也是他的作用。也就是说父模块只管理依赖的版本,并不管理依赖本身!
看看我们现在引入了这么多pom,但是项目中只有jdk的包
右键顶级工程,然后如下图
跟着提示,取你喜欢的名字,子模块就创建好了!
可以会看我们的**maven聚合工程示意图,**发现基础工程中的common,model,api是依赖的关系,所以我们要配置依赖
在model的pom文件中,去依赖common
在api的pom文件中,去依赖model
最后完成的Module骨架显示
创建一个Controller
package com.imooc.user.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @version 1.0
* @ClassName HelloController
* @Description TODO
* @Author 89255
*/
@RestController
public class HelloController {
@RequestMapping("/hello")
public Object Hello(){
return "helloworld";
}
}
application.yml
server:
#访问端口8003
port: 8003
tomcat:
#tomcat字符集UTF8
uri-encoding: UTF-8
#禁用,如果不禁用tomcat默认超过2M大小的文件不获取
max-swallow-size: -1
#配置项目信息
spring:
application:
name: service-user
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
测试结果,成功!正好把我们的虚拟网址测试了一下,发现是可以使用的,至此我们的聚合工程的基本框架就搭建好了
我们的接口都会写在子模块api中,我们在api中创建一个接口
package user.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @version 1.0
* @ClassName HelloController
* @Description TODO
* @Author 89255
*/
@RestController
public interface HelloControllerApi {
@RequestMapping("/hello")
public Object Hello();
}
在user模块中就可以去继承这个接口了!
package com.imooc.user.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import user.controller.HelloControllerApi;
/**
* @version 1.0
* @ClassName HelloController
* @Description TODO
* @Author 89255
*/
@RestController
public class HelloController implements HelloControllerApi {
@RequestMapping("/hello")
public Object Hello(){
return "helloworld";
}
}
为什么要这么做呢?
api的作用:
api 就相当于企业的领导,老板,部门经理
其他的服务层都是实现,他们就相当于员工,只做事情
老板(开发人员)来看一下每个人(服务)的进度,做什么事。老板不会去问员工,他只会对接部门经理。
那么这个里的所有的api接口就是统一在这里进行管理和调度的,微服务也是如此
运作:
现在的所有接口都在此暴露,实现都是在各自的微服务中
本项目只写接口,不写实现,实现在各自的微服务工程中,因为以业务来划分的微服务有很多controller也会分散在各个微服务工程中,一旦多了就很难统一管理和查看
其次,微服务之间的调用都是基于接口的
更多it视欢 ukoo
如果不这么做,微服务之间的调用就需要相互依赖了,
耦合度也就高了,接口的目的为了能够提供解耦。
此外,本工程的接口其实就是一套规范。实现都是由各自的工程去做的处理。目前我们使用springboot作为接口的实现的。
如果以后springboot被淘汰了,我们也不需要修改接口,只用修改实现就可以了。。
配置logback-spring.xml
别cv,记得修改日志存储路径
%white(%d{mm:ss.SSS}) %green([%thread]) %cyan(%-5level) %yellow(%logger{36}) %magenta(-) %black(%msg%n)
utf8
${LOG_HOME}/service-admin.%d{yyyy-MM-dd}.log
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
创建多个环境的yml,例如开发环境(dev),测试环境(test),生产环境(pro)
然后再application中表明你现在是哪个环境
例如现在就是dev环境
我们现在的开发环境都是前后端交互的,所以啊,你必须返回给前端一个json供他解析的,这个json就可以用对象来转换,所以我们需要创建一个公共的对象供我们返回,这个对象其实就是我们给前端的json了,例如前端怎么知道调用你的接口正确与否呢?你可以在公共返回对象中设置一个status,其中200代表正确,你每次返回给前端一个公共返回对象的String json形式,前端解析你的json,完事!
根据我们上边的说法,很容易就可以把这个公共返回类写出来了。。。
package com.imooc.grace.result;
/**
*
* @Title: JsonResult.java
* @Package com.imooc.utils
* @Description: 自定义响应数据结构
* 本类可提供给 H5/ios/安卓/公众号/小程序 使用
* 前端接受此类数据(json object)后,可自行根据业务去实现相关功能
*
* 200:表示成功
* 500:表示错误,错误信息在msg字段中
* 501:bean验证错误,不管多少个错误都以map形式返回
* 502:拦截器拦截到用户token出错
* 555:异常抛出信息
* 556: 用户qq校验异常
* 557: 校验用户是否在CAS登录,用户门票的校验
* @Copyright: Copyright (c) 2020
* @Company: www.imooc.com
* @author 慕课网 - 风间影月
* @version V1.0
*/
public class JsonResult {
// 响应业务状态
private Integer status;
// 响应消息
private String msg;
// 响应中的数据
private Object data;
private String ok; // 不使用
public static JsonResult build(Integer status, String msg, Object data) {
return new JsonResult(status, msg, data);
}
public static JsonResult build(Integer status, String msg, Object data, String ok) {
return new JsonResult(status, msg, data, ok);
}
public static JsonResult ok(Object data) {
return new JsonResult(data);
}
public static JsonResult ok() {
return new JsonResult(null);
}
public static JsonResult errorMsg(String msg) {
return new JsonResult(500, msg, null);
}
public static JsonResult errorUserTicket(String msg) {
return new JsonResult(557, msg, null);
}
public static JsonResult errorMap(Object data) {
return new JsonResult(501, "error", data);
}
public static JsonResult errorTokenMsg(String msg) {
return new JsonResult(502, msg, null);
}
public static JsonResult errorException(String msg) {
return new JsonResult(555, msg, null);
}
public static JsonResult errorUserQQ(String msg) {
return new JsonResult(556, msg, null);
}
public JsonResult() {
}
public JsonResult(Integer status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public JsonResult(Integer status, String msg, Object data, String ok) {
this.status = status;
this.msg = msg;
this.data = data;
this.ok = ok;
}
public JsonResult(Object data) {
this.status = 200;
this.msg = "OK";
this.data = data;
}
public Boolean isOK() {
return this.status == 200;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getOk() {
return ok;
}
public void setOk(String ok) {
this.ok = ok;
}
}
写完了用Controller返回一个我们的公共返回对象试试
成功!
其实不用把所有的错误方法写到一个公共返回类里,可以创建一个枚举类,然后让公共返回类去使用枚举类的数据,就可以解耦,也可以更优雅
GraceJsonResult
package result;
import java.util.Map;
/**
* @version 1.0
* @ClassName GraceJsonResult
* @Description TODO
* @Author 89255
*/
public class GraceJsonResult {
// 响应业务状态码
private Integer status;
// 响应消息
private String msg;
// 是否成功
private Boolean success;
// 响应数据,可以是Object,也可以是List或Map等
private Object data;
/**
* 成功返回,带有数据的,直接往OK方法丢data数据即可
* @param data
* @return
*/
public static GraceJsonResult ok(Object data) {
return new GraceJsonResult(data);
}
/**
* 成功返回,不带有数据的,直接调用ok方法,data无须传入(其实就是null)
* @return
*/
public static GraceJsonResult ok() {
return new GraceJsonResult(ResponseStatusEnum.SUCCESS);
}
public GraceJsonResult(Object data) {
this.status = ResponseStatusEnum.SUCCESS.status();
this.msg = ResponseStatusEnum.SUCCESS.msg();
this.success = ResponseStatusEnum.SUCCESS.success();
this.data = data;
}
/**
* 错误返回,直接调用error方法即可,当然也可以在ResponseStatusEnum中自定义错误后再返回也都可以
* @return
*/
public static GraceJsonResult error() {
return new GraceJsonResult(ResponseStatusEnum.FAILED);
}
/**
* 错误返回,map中包含了多条错误信息,可以用于表单验证,把错误统一的全部返回出去
* @param map
* @return
*/
public static GraceJsonResult errorMap(Map map) {
return new GraceJsonResult(ResponseStatusEnum.FAILED, map);
}
/**
* 错误返回,直接返回错误的消息
* @param msg
* @return
*/
public static GraceJsonResult errorMsg(String msg) {
return new GraceJsonResult(ResponseStatusEnum.FAILED, msg);
}
/**
* 错误返回,token异常,一些通用的可以在这里统一定义
* @return
*/
public static GraceJsonResult errorTicket() {
return new GraceJsonResult(ResponseStatusEnum.TICKET_INVALID);
}
/**
* 自定义错误范围,需要传入一个自定义的枚举,可以到[ResponseStatusEnum.java[中自定义后再传入
* @param responseStatus
* @return
*/
public static GraceJsonResult errorCustom(ResponseStatusEnum responseStatus) {
return new GraceJsonResult(responseStatus);
}
public static GraceJsonResult exception(ResponseStatusEnum responseStatus) {
return new GraceJsonResult(responseStatus);
}
public GraceJsonResult(ResponseStatusEnum responseStatus) {
this.status = responseStatus.status();
this.msg = responseStatus.msg();
this.success = responseStatus.success();
}
public GraceJsonResult(ResponseStatusEnum responseStatus, Object data) {
this.status = responseStatus.status();
this.msg = responseStatus.msg();
this.success = responseStatus.success();
this.data = data;
}
public GraceJsonResult(ResponseStatusEnum responseStatus, String msg) {
this.status = responseStatus.status();
this.msg = msg;
this.success = responseStatus.success();
}
public GraceJsonResult() {
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
}
ResposenEnum
package result;
/**
* @version 1.0
* @ClassName ResponseStatusEnum
* @Description TODO
* @Author 89255
*/
public enum ResponseStatusEnum {
SUCCESS(200, true, "操作成功!"),
FAILED(500, false, "操作失败!"),
// 50x
UN_LOGIN(501,false,"请登录后再继续操作!"),
TICKET_INVALID(502,false,"会话失效,请重新登录!"),
NO_AUTH(503,false,"您的权限不足,无法继续操作!"),
MOBILE_ERROR(504,false,"短信发送失败,请稍后重试!"),
SMS_NEED_WAIT_ERROR(505,false,"短信发送太快啦~请稍后再试!"),
SMS_CODE_ERROR(506,false,"验证码过期或不匹配,请稍后再试!"),
USER_FROZEN(507,false,"用户已被冻结,请联系管理员!"),
USER_UPDATE_ERROR(508,false,"用户信息更新失败,请联系管理员!"),
USER_INACTIVE_ERROR(509,false,"请前往[账号设置]修改信息激活后再进行后续操作!"),
FILE_UPLOAD_NULL_ERROR(510,false,"文件不能为空,请选择一个文件再上传!"),
FILE_UPLOAD_FAILD(511,false,"文件上传失败!"),
FILE_FORMATTER_FAILD(512,false,"文件图片格式不支持!"),
FILE_MAX_SIZE_ERROR(513,false,"仅支持500kb大小以下的图片上传!"),
FILE_NOT_EXIST_ERROR(514,false,"你所查看的文件不存在!"),
USER_STATUS_ERROR(515,false,"用户状态参数出错!"),
USER_NOT_EXIST_ERROR(516,false,"用户不存在!"),
// 自定义系统级别异常 54x
SYSTEM_INDEX_OUT_OF_BOUNDS(541, false, "系统错误,数组越界!"),
SYSTEM_ARITHMETIC_BY_ZERO(542, false, "系统错误,无法除零!"),
SYSTEM_NULL_POINTER(543, false, "系统错误,空指针!"),
SYSTEM_NUMBER_FORMAT(544, false, "系统错误,数字转换异常!"),
SYSTEM_PARSE(545, false, "系统错误,解析异常!"),
SYSTEM_IO(546, false, "系统错误,IO输入输出异常!"),
SYSTEM_FILE_NOT_FOUND(547, false, "系统错误,文件未找到!"),
SYSTEM_CLASS_CAST(548, false, "系统错误,类型强制转换错误!"),
SYSTEM_PARSER_ERROR(549, false, "系统错误,解析出错!"),
SYSTEM_DATE_PARSER_ERROR(550, false, "系统错误,日期解析出错!"),
// admin 管理系统 56x
ADMIN_USERNAME_NULL_ERROR(561, false, "管理员登录名不能为空!"),
ADMIN_USERNAME_EXIST_ERROR(562, false, "管理员登录名已存在!"),
ADMIN_NAME_NULL_ERROR(563, false, "管理员负责人不能为空!"),
ADMIN_PASSWORD_ERROR(564, false, "密码不能为空后者两次输入不一致!"),
ADMIN_CREATE_ERROR(565, false, "添加管理员失败!"),
ADMIN_PASSWORD_NULL_ERROR(566, false, "密码不能为空!"),
ADMIN_NOT_EXIT_ERROR(567, false, "管理员不存在或密码错误!"),
ADMIN_FACE_NULL_ERROR(568, false, "人脸信息不能为空!"),
ADMIN_FACE_LOGIN_ERROR(569, false, "人脸识别失败,请重试!"),
CATEGORY_EXIST_ERROR(570, false, "文章分类已存在,请换一个分类名!"),
// 媒体中心 相关错误 58x
ARTICLE_COVER_NOT_EXIST_ERROR(580, false, "文章封面不存在,请选择一个!"),
ARTICLE_CATEGORY_NOT_EXIST_ERROR(581, false, "请选择正确的文章领域!"),
ARTICLE_CREATE_ERROR(582, false, "创建文章失败,请重试或联系管理员!"),
ARTICLE_QUERY_PARAMS_ERROR(583, false, "文章列表查询参数错误!"),
ARTICLE_DELETE_ERROR(584, false, "文章删除失败!"),
ARTICLE_WITHDRAW_ERROR(585, false, "文章撤回失败!"),
ARTICLE_REVIEW_ERROR(585, false, "文章审核出错!"),
ARTICLE_ALREADY_READ_ERROR(586, false, "文章重复阅读!"),
// 人脸识别错误代码
FACE_VERIFY_TYPE_ERROR(600, false, "人脸比对验证类型不正确!"),
FACE_VERIFY_LOGIN_ERROR(601, false, "人脸登录失败!"),
// 系统错误,未预期的错误 555
SYSTEM_ERROR(555, false, "系统繁忙,请稍后再试!"),
SYSTEM_OPERATION_ERROR(556, false, "操作失败,请重试或联系管理员"),
SYSTEM_RESPONSE_NO_INFO(557, false, "");
// 响应业务状态
private Integer status;
// 调用是否成功
private Boolean success;
// 响应消息,可以为成功或者失败的消息
private String msg;
ResponseStatusEnum(Integer status, Boolean success, String msg) {
this.status = status;
this.success = success;
this.msg = msg;
}
public Integer status() {
return status;
}
public Boolean success() {
return success;
}
public String msg() {
return msg;
}
}
什么叫逆向生成呢。。我们知道当我们写业务代码的时候,通常要根据数据库的库来书写很多的包,service,controller,mapper,也会有很多的mybatis映射文件,这个逆向生成就帮我们省去了这些麻烦,会帮我们自动生成文件,十分便捷! 可以去了解一下mybatis-plus的generator,非常强大
首先将mybatis-gennerator-databases文件拷贝到目录下,这个其实可以去博客上找
结果拷贝过去一看,诶?怎么是黑色的,裂开
这个时候就可以打开idea旁边的maven,点击+号,然后在选中黑色的文件夹中的pom文件,idea就会帮我们处理好啦
然后修改一下generatorConfig-user.xml配置文件
主要使我们的url,和包名(如果你用的包名不一致是要修改的),我只修改了url图方便
最后点击UserGenerator的运行按钮,就自动生成了!真神奇!
点进去检查一下,发现属性和数据库中的字段都是一致的,区别就是数据库中是下划线,java是驼峰命名
导入相关依赖
mysql
mysql-connector-java
${mysql-connector-java.version}
org.mybatis.spring.boot
mybatis-spring-boot-starter
${mybatis-spring-boot-starter.version}
tk.mybatis
mapper-spring-boot-starter
${mapper-spring-boot-starter.version}
com.github.pagehelper
pagehelper-spring-boot-starter
${pagehelper-spring-boot-starter.version}
将逆向工程生成的pojo拷贝到Model里就好了,mapper放在user里,MyMapper的interface就放在存放接口的Api中(在mapper中的例如UserMqpper是实现MyMapper的)
在yml文件中配置数据源啥的
datasource: # 数据源的相关配置
type: com.zaxxer.hikari.HikariDataSource # 数据源类型:HikariCP # mysql驱动
url: jdbc:mysql://localhost:3306/mooc_project?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
username: root
password: 123456
hikari:
connection-timeout: 30000 # 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 默认:30秒
minimum-idle: 5 # 最小连接数
maximum-pool-size: 20 # 最大连接数
auto-commit: true # 自动提交
idle-timeout: 600000 # 连接超时的最大时长(毫秒),超时则被释放(retired),默认:10分钟
pool-name: DateSourceHikariCP # 连接池名字
max-lifetime: 1800000 # 连接的生命时长(毫秒),超时而且没被使用则被释放(retired),默认:30分钟 1800000ms
connection-test-query: SELECT 1
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
driver-class-name: com.mysql.jdbc.Driver
mybatis:
type-aliases-package: com.imooc.com.imooc.pojo # 所有POJO类所在包路径
mapper-locations: classpath:mapper/*.xml # mapper映射文件
启动后发现报错,是因为没有加上mapperscan,没扫描到mapper接口,我们在启动类头上加mapperscan扫描我们的mapper接口所处的包就行了。。
配置swagger配置类
package com.imooc.api.config;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.RequestHandler;
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;
/**
* @version 1.0
* @ClassName Swagger2
* @Description TODO
* @Author 89255
*/
@Configuration
@EnableSwagger2
public class Swagger2 {
// http://localhost:8088/swagger-ui.html 原路径
// http://localhost:8088/doc.html 新路径
// 配置swagger2核心配置 docket
@Bean
public Docket createRestApi() {
Predicate adminPredicate = RequestHandlerSelectors.basePackage("com.imooc.admin.controller");
// Predicate articlePredicate = RequestHandlerSelectors.basePackage("com.imooc.article.controller");
Predicate userPredicate = RequestHandlerSelectors.basePackage("com.imooc.user.controller");
Predicate filesPredicate = RequestHandlerSelectors.basePackage("com.imooc.files.controller");
return new Docket(DocumentationType.SWAGGER_2) // 指定api类型为swagger2
.apiInfo(apiInfo()) // 用于定义api文档汇总信息
.select()
.apis(Predicates.or(userPredicate, adminPredicate, filesPredicate))
// .apis(Predicates.or(adminPredicate, articlePredicate, userPredicate, filesPredicate))
.paths(PathSelectors.any()) // 所有controller
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("项目api") // 文档页标题
.contact(new Contact("phm",
"https://blog.csdn.net/qq_46004291?spm=1001.2101.3001.5343",
"[email protected]")) // 联系人信息
.description("一个慕课网的项目") // 详细信息
.version("1.0.1") // 文档版本号
.termsOfServiceUrl("https://blog.csdn.net/qq_46004291?spm=1001.2101.3001.5343") // 网站地址
.build();
}
}
注意swagger测试的api,因为我们的controller的包是这样的com.imooc.user.controller下写我们的接口,这样的话我们就得写多个Predicate对象,因为我们不是controller包下分user,admin。