赞
踩
各位爷,完整项目gitee如下,求star
随着智能手机的普及,人们更加习惯于通过手机来看新闻。由于生活节奏的加快,很多人只能利用碎片时间来获取信息,因此,对于移动资讯客户端的需求也越来越高。
黑马头条项目正是在这样背景下开发出来。黑马头条项目采用当下火热的微服务+大数据技术架构实现。本项目主要着手于获取最新最热新闻资讯,通过大数据分析用户喜好精确推送咨询新闻
项目演示地址:
平台管理与自媒体为PC端,用电脑浏览器打开即可。
其中app端为移动端,打开方式有两种:
谷歌浏览器打开,调成移动端模式
手机浏览器打开或扫描右侧二维码
Spring-Cloud-Gateway : 微服务之前架设的网关服务,实现服务注册中的API请求路由,以及控制流速控制和熔断处理都是常用的架构手段,而这些功能Gateway天然支持
运用Spring Boot快速开发框架,构建项目工程;并结合Spring Cloud全家桶技术,实现后端个人中心、自媒体、管理中心等微服务。
运用Spring Cloud Alibaba Nacos作为项目中的注册中心和配置中心
运用mybatis-plus作为持久层提升开发效率
运用Kafka完成内部系统消息通知;与客户端系统消息通知;以及实时数据计算
运用Redis缓存技术,实现热数据的计算,提升系统性能指标
使用Mysql存储用户数据,以保证上层数据查询的高性能
使用Mongo存储用户热数据,以保证用户热数据高扩展和高性能指标
使用FastDFS作为静态资源存储器,在其上实现热静态资源缓存、淘汰等功能
运用Hbase技术,存储系统中的冷数据,保证系统数据的可靠性
运用ES搜索技术,对冷数据、文章数据建立索引,以保证冷数据、文章查询性能
运用AI技术,来完成系统自动化功能,以提升效率及节省成本。比如实名认证自动化
PMD&P3C : 静态代码扫描工具,在项目中扫描项目代码,检查异常点、优化点、代码规范等,为开发团队提供规范统一,提升项目代码质量
1)打开当天资料文件中的镜像,拷贝到一个地方,然后解压
2)解压后,双击ContOS7-hmtt.vmx文件,前提是电脑上已经安装了VMware
修改虚拟网络地址(NAT)
①,选中VMware中的编辑
②,选择虚拟网络编辑器
③,找到NAT网卡,把网段改为200(当前挂载的虚拟机已固定ip地址)
4)修改虚拟机的网络模式为NAT
5)启动虚拟机,用户名:root 密码:itcast,当前虚拟机的ip已手动固定(静态IP), 地址为:192.168.200.130
6)使用FinalShell客户端链接
①:docker拉取镜像
docker pull nacos/nacos-server:1.2.0
②:创建容器
docker run --env MODE=standalone --name nacos --restart=always -d -p 8848:8848 nacos/nacos-server:1.2.0
MODE=standalone 单机版
--restart=always 开机启动
-p 8848:8848 映射端口
-d 创建一个守护式容器在后台运行
③:访问地址:http://192.168.200.130:8848/nacos
①:项目依赖环境(需提前安装好)
JDK1.8
Intellij Idea
maven-3.6.1
Git
②:在当天资料中解压heima-leadnews.zip文件,拷贝到 没有中文和空格的目录,使用idea打开即可
③:IDEA开发工具配置
设置本地仓库,建议使用资料中提供好的仓库
④:设置项目编码格式
户点击开始使用
登录后的用户权限较大,可以查 看,也可以操作(点赞,关注,评论)
用户点击不登录,先看看
游客只有查看的权限
关于app端用户相关的内容较多,可以单独设置一个库leadnews_user
表名称 | 说明 |
ap_user | APP用户信息表 |
ap_user_fan | APP用户粉丝信息表 |
ap_user_follow | APP用户关注信息表 |
ap_user_realname | APP实名认证信息表 |
从当前资料中找到对应数据库并导入到mysql中
登录需要用到的是ap_user表,表结构如下:
项目中的持久层使用的mybatis-plus,一般都使用mybais-plus逆向生成对应的实体类
app_user表对应的实体类如下:
-
- package com.heima.model.user.pojos;
-
- import com.baomidou.mybatisplus.annotation.IdType;
- import com.baomidou.mybatisplus.annotation.TableField;
- import com.baomidou.mybatisplus.annotation.TableId;
- import com.baomidou.mybatisplus.annotation.TableName;
- import lombok.Data;
-
- import java.io.Serializable;
- import java.util.Date;
-
- /**
- * <p>
- * APP用户信息表
- * </p>
- *
- * @author itheima
- */
- @Data
- @TableName("ap_user")
- public class ApUser implements Serializable {
-
- private static final long serialVersionUID = 1L;
-
- /**
- * 主键
- */
- @TableId(value = "id", type = IdType.AUTO)
- private Integer id;
-
- /**
- * 密码、通信等加密盐
- */
- @TableField("salt")
- private String salt;
-
- /**
- * 用户名
- */
- @TableField("name")
- private String name;
-
- /**
- * 密码,md5加密
- */
- @TableField("password")
- private String password;
-
- /**
- * 手机号
- */
- @TableField("phone")
- private String phone;
-
- /**
- * 头像
- */
- @TableField("image")
- private String image;
-
- /**
- * 0 男
- 1 女
- 2 未知
- */
- @TableField("sex")
- private Boolean sex;
-
- /**
- * 0 未
- 1 是
- */
- @TableField("is_certification")
- private Boolean certification;
-
- /**
- * 是否身份认证
- */
- @TableField("is_identity_authentication")
- private Boolean identityAuthentication;
-
- /**
- * 0正常
- 1锁定
- */
- @TableField("status")
- private Boolean status;
-
- /**
- * 0 普通用户
- 1 自媒体人
- 2 大V
- */
- @TableField("flag")
- private Short flag;
-
- /**
- * 注册时间
- */
- @TableField("created_time")
- private Date createdTime;
-
- }
手动加密(md5+随机字符串)
md5是不可逆加密,md5相同的密码每次加密都一样,不太安全。在md5的基础上手动加盐(salt)处理
注册->生成盐
登录->使用盐来配合验证
1,用户输入了用户名和密码进行登录,校验成功后返回jwt(基于当前用户的id生成)
2,用户游客登录,生成jwt返回(基于默认值0生成)‘
在heima-leadnews-service下创建工程heima-leadnews-user
引导类
-
- package com.heima.user;
-
- import org.mybatis.spring.annotation.MapperScan;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
-
- @SpringBootApplication
- @EnableDiscoveryClient//集成当前注册中心
- @MapperScan("com.heima.user.mapper")//扫描mapper
- public class UserApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(UserApplication.class,args);
- }
- }
bootstrap.yml
- server:
- port: 51801
- spring:
- application:
- name: leadnews-user
- cloud:
- nacos:
- discovery:
- server-addr: 192.168.200.130:8848
- config:
- server-addr: 192.168.200.130:8848
- file-extension: yml
在nacos中创建配置文件
- spring:
- datasource:
- driver-class-name: com.mysql.jdbc.Driver
- url: jdbc:mysql://localhost:3306/leadnews_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
- username: root
- password: root
- # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
- mybatis-plus:
- mapper-locations: classpath*:mapper/*.xml
- # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
- type-aliases-package: com.heima.model.user.pojos
The last packet successfully received from the server was 523 milliseconds ago. The last packet sent successfully to the server was 518 milliseconds ago.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
可以把nacos的mysqlurl改成下面
url: jdbc:mysql://localhost:3306/leadnews_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
logback.xml
- <?xml version="1.0" encoding="UTF-8"?>
-
- <configuration>
- <!--定义日志文件的存储地址,使用绝对路径-->
- <property name="LOG_HOME" value="e:/logs"/>
-
- <!-- Console 输出设置 -->
- <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
- <encoder>
- <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
- <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
- <charset>utf8</charset>
- </encoder>
- </appender>
-
- <!-- 按照每天生成日志文件 -->
- <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
- <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
- <!--日志文件输出的文件名-->
- <fileNamePattern>${LOG_HOME}/leadnews.%d{yyyy-MM-dd}.log</fileNamePattern>
- </rollingPolicy>
- <encoder>
- <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
- </encoder>
- </appender>
-
- <!-- 异步输出 -->
- <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
- <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
- <discardingThreshold>0</discardingThreshold>
- <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
- <queueSize>512</queueSize>
- <!-- 添加附加的appender,最多只能添加一个 -->
- <appender-ref ref="FILE"/>
- </appender>
-
- <logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="DEBUG" additivity="false">
- <appender-ref ref="CONSOLE"/>
- </logger>
- <logger name="org.springframework.boot" level="debug"/>
- <root level="info">
- <!--<appender-ref ref="ASYNC"/>-->
- <appender-ref ref="FILE"/>
- <appender-ref ref="CONSOLE"/>
- </root>
- </configuration>
①:接口定义
-
- @RestController
- @RequestMapping("/api/v1/login")
- public class ApUserLoginController {
-
- @PostMapping("/login_auth")
- public ResponseResult login(@RequestBody LoginDto dto) {
- return null;
- }
- }
②:持久层mapper
-
- package com.heima.user.mapper;
-
- import com.baomidou.mybatisplus.core.mapper.BaseMapper;
- import com.heima.model.user.pojos.ApUser;
- import org.apache.ibatis.annotations.Mapper;
-
- @Mapper
- public interface ApUserMapper extends BaseMapper<ApUser> {
- }
③:业务层service
-
- package com.heima.user.service;
-
- import com.baomidou.mybatisplus.extension.service.IService;
- import com.heima.model.common.dtos.ResponseResult;
- import com.heima.model.user.dtos.LoginDto;
- import com.heima.model.user.pojos.ApUser;
-
- public interface ApUserService extends IService<ApUser>{
-
- /**
- * app端登录
- * @param dto
- * @return
- */
- public ResponseResult login(LoginDto dto);
-
- }
实现类:
-
- package com.heima.user.service.impl;
-
- import com.baomidou.mybatisplus.core.toolkit.Wrappers;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import com.heima.model.common.dtos.ResponseResult;
- import com.heima.model.common.enums.AppHttpCodeEnum;
- import com.heima.model.user.dtos.LoginDto;
- import com.heima.model.user.pojos.ApUser;
- import com.heima.user.mapper.ApUserMapper;
- import com.heima.user.service.ApUserService;
- import com.heima.utils.common.AppJwtUtil;
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.stereotype.Service;
- import org.springframework.util.DigestUtils;
-
- import java.util.HashMap;
- import java.util.Map;
-
-
- @Service
- public class ApUserServiceImpl extends ServiceImpl<ApUserMapper, ApUser> implements ApUserService {
-
- @Override
- public ResponseResult login(LoginDto dto) {
-
- //1.正常登录(手机号+密码登录)
- if (!StringUtils.isBlank(dto.getPhone()) && !StringUtils.isBlank(dto.getPassword())) {
- //1.1查询用户
- ApUser apUser = getOne(Wrappers.<ApUser>lambdaQuery().eq(ApUser::getPhone, dto.getPhone()));
- if (apUser == null) {
- return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"用户不存在");
- }
-
- //1.2 比对密码
- String salt = apUser.getSalt();
- String pswd = dto.getPassword();
- pswd = DigestUtils.md5DigestAsHex((pswd + salt).getBytes());
- if (!pswd.equals(apUser.getPassword())) {
- return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
- }
- //1.3 返回数据 jwt
- Map<String, Object> map = new HashMap<>();
- map.put("token", AppJwtUtil.getToken(apUser.getId().longValue()));
- //这两个值 置空 再返回对象
- apUser.setSalt("");
- apUser.setPassword("");
- map.put("user", apUser);
- return ResponseResult.okResult(map);
- } else {
- //2.游客 同样返回token id = 0
- Map<String, Object> map = new HashMap<>();
- map.put("token", AppJwtUtil.getToken(0l));
- return ResponseResult.okResult(map);
- }
- }
- }
④:控制层controller
-
- package com.heima.user.controller.v1;
-
- import com.heima.model.common.dtos.ResponseResult;
- import com.heima.model.user.dtos.LoginDto;
- import com.heima.user.service.ApUserService;
- import org.springframework.beans.factory.annotation.Autowired;
- 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.RestController;
-
- @RestController
- @RequestMapping("/api/v1/login")
- public class ApUserLoginController {
-
- @Autowired
- private ApUserService apUserService;
-
- @PostMapping("/login_auth")
- public ResponseResult login(@RequestBody LoginDto dto) {
- return apUserService.login(dto);
- }
- }
docker exec -it redis redis-cli这个进入服务器得redis界面 你再输入docker ps就能看到服务起来了;docker 中的redis没密码,要把nacos的密码去掉;
报错的兄弟们先创建redis容器a然后把nacos配置中redis密码那一行注掉就行了
docker run --name redis -p 6379:6379 -d redis
Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件。postman被500万开发者和超100,000家公司用于每月访问1.3亿个API。
官方网址:Postman
解压资料文件夹中的软件,安装即可
通常的接口测试查看请求和响应,下面是登录请求的测试
http://localhost:51801/api/v1/login/login_auth
{"phone":"13511223456","password":"admin"}
(1)简介
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务(API Documentation & Design Tools for Teams | Swagger)。 它的主要作用是:
使得前后端分离开发更加方便,有利于团队协作
接口的文档在线自动生成,降低后端开发人员编写接口文档的负担
功能测试
Spring已经将Swagger纳入自身的标准,建立了Spring-swagger项目,现在叫Springfox。
通过在项目中引入Springfox ,即可非常简单快捷的使用Swagger。
(2)SpringBoot集成Swagger
引入依赖,在heima-leadnews-model和heima-leadnews-common模块中引入该依赖
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger2</artifactId>
- </dependency>
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger-ui</artifactId>
- </dependency>
只需要在heima-leadnews-common中进行配置即可,因为其他微服务工程都直接或间接依赖即可。
在heima-leadnews-common工程中添加一个配置类
新增:com.heima.common.swagger.SwaggerConfiguration
-
- package com.heima.common.swagger;
-
- 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;
-
- @Configuration
- @EnableSwagger2
- public class SwaggerConfiguration {
-
- @Bean
- public Docket buildDocket() {
- return new Docket(DocumentationType.SWAGGER_2)
- .apiInfo(buildApiInfo())
- .select()
- // 要扫描的API(Controller)基础包
- .apis(RequestHandlerSelectors.basePackage("com.heima"))
- .paths(PathSelectors.any())
- .build();
- }
-
- private ApiInfo buildApiInfo() {
- Contact contact = new Contact("黑马程序员","","");
- return new ApiInfoBuilder()
- .title("黑马头条-平台管理API文档")
- .description("黑马头条后台api")
- .contact(contact)
- .version("1.0.0").build();
- }
- }
在heima-leadnews-common模块中的resources目录中新增以下目录和文件
文件:resources/META-INF/Spring.factories
-
- org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- com.heima.common.swagger.SwaggerConfiguration
(3)Swagger常用注解
在Java类中添加Swagger的注解即可生成Swagger接口文档,常用Swagger注解如下:
@Api:修饰整个类,描述Controller的作用
@ApiOperation:描述一个类的一个方法,或者说一个接口
@ApiParam:单个参数的描述信息
@ApiModel:用对象来接收参数
@ApiModelProperty:用对象接收参数时,描述对象的一个字段
@ApiResponse:HTTP响应其中1个描述
@ApiResponses:HTTP响应整体描述
@ApiIgnore:使用该注解忽略这个API
@ApiError :发生错误返回的信息
@ApiImplicitParam:一个请求参数
@ApiImplicitParams:多个请求参数的描述信息
@ApiImplicitParam属性:
属性 | 取值 | 作用 |
paramType | 查询参数类型 | |
path | 以地址的形式提交数据 | |
query | 直接跟参数完成自动映射赋值 | |
body | 以流的形式提交 仅支持POST | |
header | 参数在request headers 里边提交 | |
form | 以form表单的形式提交 仅支持POST | |
dataType | 参数的数据类型 只作为标志说明,并没有实际验证 | |
Long | ||
String | ||
name | 接收参数名 | |
value | 接收参数的意义描述 | |
required | 参数是否必填 | |
true | 必填 | |
false | 非必填 | |
defaultValue | 默认值 |
我们在ApUserLoginController中添加Swagger注解,代码如下所示:
-
- @RestController
- @RequestMapping("/api/v1/login")
- @Api(value = "app端用户登录", tags = "ap_user", description = "app端用户登录API")
- public class ApUserLoginController {
-
- @Autowired
- private ApUserService apUserService;
-
- @PostMapping("/login_auth")
- @ApiOperation("用户登录")
- public ResponseResult login(@RequestBody LoginDto dto){
- return apUserService.login(dto);
- }
- }
LoginDto
-
- @Data
- public class LoginDto {
-
- /**
- * 手机号
- */
- @ApiModelProperty(value="手机号",required = true)
- private String phone;
-
- /**
- * 密码
- */
- @ApiModelProperty(value="密码",required = true)
- private String password;
- }
启动user微服务,访问地址:http://localhost:51801/swagger-ui.html
(1)简介
knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍!
gitee地址:knife4j: Knife4j是一个集Swagger2 和 OpenAPI3为一体的增强解决方案
官方文档:Knife4j · 集Swagger2及OpenAPI3为一体的增强解决方案. | Knife4j
效果演示:http://knife4j.xiaominfo.com/doc.html
(2)核心功能
该UI增强包主要包括两大核心功能:文档说明 和 在线调试
文档说明:根据Swagger的规范说明,详细列出接口文档的说明,包括接口地址、类型、请求示例、请求参数、响应示例、响应参数、响应码等信息,使用swagger-bootstrap-ui能根据该文档说明,对该接口的使用情况一目了然。
在线调试:提供在线接口联调的强大功能,自动解析当前接口参数,同时包含表单验证,调用参数可返回接口响应内容、headers、Curl请求命令实例、响应时间、响应状态码等信息,帮助开发者在线调试,而不必通过其他测试工具测试接口是否正确,简介、强大。
个性化配置(Swaager无):通过个性化ui配置项,可自定义UI的相关显示信息
离线文档(Swaager无):根据标准规范,生成的在线markdown离线文档,开发者可以进行拷贝生成markdown接口文档,通过其他第三方markdown转换工具转换成html或pdf,这样也可以放弃swagger2markdown组件
接口排序:自1.8.5后,ui支持了接口排序功能,
例如一个注册功能主要包含了多个步骤,可以根据swagger-bootstrap-ui提供的接口排序规则实现接口的排序,step化接口操作,方便其他开发者进行接口对接
(3)快速集成
在heima-leadnews-common模块中的pom.xml文件中引入knife4j的依赖,如下:
- <dependency>
- <groupId>com.github.xiaoymin</groupId>
- <artifactId>knife4j-spring-boot-starter</artifactId>
- </dependency>
创建Swagger配置文件
在heima-leadnews-common模块中新建配置类
新建Swagger的配置文件SwaggerConfiguration.java文件,创建springfox提供的Docket分组对象,代码如下:
-
- package com.heima.common.knife4j;
-
- import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.context.annotation.Import;
- import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
- import springfox.documentation.builders.ApiInfoBuilder;
- import springfox.documentation.builders.PathSelectors;
- import springfox.documentation.builders.RequestHandlerSelectors;
- import springfox.documentation.service.ApiInfo;
- import springfox.documentation.spi.DocumentationType;
- import springfox.documentation.spring.web.plugins.Docket;
- import springfox.documentation.swagger2.annotations.EnableSwagger2;
-
- @Configuration
- @EnableSwagger2
- @EnableKnife4j
- @Import(BeanValidatorPluginsConfiguration.class)
- public class Swagger2Configuration {
-
- @Bean(value = "defaultApi2")
- public Docket defaultApi2() {
- Docket docket=new Docket(DocumentationType.SWAGGER_2)
- .apiInfo(apiInfo())
- //分组名称
- .groupName("1.0")
- .select()
- //这里指定Controller扫描包路径
- .apis(RequestHandlerSelectors.basePackage("com.heima"))
- .paths(PathSelectors.any())
- .build();
- return docket;
- }
- private ApiInfo apiInfo() {
- return new ApiInfoBuilder()
- .title("黑马头条API文档")
- .description("黑马头条API文档")
- .version("1.0")
- .build();
- }
- }
以上有两个注解需要特别说明,如下表:
注解 | 说明 |
@EnableSwagger2 | 该注解是Springfox-swagger框架提供的使用Swagger注解,该注解必须加 |
@EnableKnife4j | 该注解是knife4j提供的增强注解,Ui提供了例如动态参数、参数过滤、接口排序等增强功能,如果你想使用这些增强功能就必须加该注解,否则可以不用加 |
添加配置
在Spring.factories中新增配置
-
- org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- com.heima.common.swagger.Swagger2Configuration, \
- com.heima.common.swagger.SwaggerConfiguration
访问
在浏览器输入地址:http://host:port/doc.html;http://localhost:51801/doc.html
(1)在heima-leadnews-gateway导入以下依赖
pom文件
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-gateway</artifactId>
- </dependency>
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
- </dependency>
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
- </dependency>
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- </dependency>
- </dependencies>
(2)在heima-leadnews-gateway下创建heima-leadnews-app-gateway微服务
引导类:
-
- package com.heima.app.gateway;
-
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
-
- @SpringBootApplication
- @EnableDiscoveryClient //开启注册中心
- public class AppGatewayApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(AppGatewayApplication.class,args);
- }
- }
bootstrap.yml
- server:
- port: 51601
- spring:
- application:
- name: leadnews-app-gateway
- cloud:
- nacos:
- discovery:
- server-addr: 192.168.200.130:8848
- config:
- server-addr: 192.168.200.130:8848
- file-extension: yml
在nacos的配置中心创建dataid为leadnews-app-gateway的yml配置
- spring:
- cloud:
- gateway:
- globalcors:
- add-to-simple-url-handler-mapping: true
- corsConfigurations:
- '[/**]':
- allowedHeaders: "*"
- allowedOrigins: "*"
- allowedMethods:
- - GET
- - POST
- - DELETE
- - PUT
- - OPTION
- routes:
- # 平台管理
- - id: user
- uri: lb://leadnews-user
- predicates:
- - Path=/user/**
- filters:
- - StripPrefix= 1
环境搭建完成以后,启动项目网关和用户两个服务,使用postman进行测试
请求地址:http://localhost:51601/user/api/v1/login/login_auth
思路分析:
用户进入网关开始登陆,网关过滤器进行判断,如果是登录,则路由到后台管理微服务进行登录
用户登录成功,后台管理微服务签发JWT TOKEN信息返回给用户
用户再次进入网关开始访问,网关过滤器接收用户携带的TOKEN
网关过滤器解析TOKEN ,判断是否有权限,如果有,则放行,如果没有则返回未认证错误
具体实现:
第一:
在认证过滤器中需要用到jwt的解析,所以需要把工具类拷贝一份到网关微服务
第二:
在网关微服务中新建全局过滤器:
-
- package com.heima.app.gateway.filter;
-
-
- import com.heima.app.gateway.util.AppJwtUtil;
- import io.jsonwebtoken.Claims;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.commons.lang.StringUtils;
- import org.springframework.cloud.gateway.filter.GatewayFilterChain;
- import org.springframework.cloud.gateway.filter.GlobalFilter;
- import org.springframework.core.Ordered;
- import org.springframework.http.HttpStatus;
- import org.springframework.http.server.reactive.ServerHttpRequest;
- import org.springframework.http.server.reactive.ServerHttpResponse;
- import org.springframework.stereotype.Component;
- import org.springframework.web.server.ServerWebExchange;
- import reactor.core.publisher.Mono;
-
- @Component
- @Slf4j
- public class AuthorizeFilter implements Ordered, GlobalFilter {
- @Override
- public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
- //1.获取request和response对象
- ServerHttpRequest request = exchange.getRequest();
- ServerHttpResponse response = exchange.getResponse();
-
- //2.判断是否是登录
- if(request.getURI().getPath().contains("/login")){
- //放行
- return chain.filter(exchange);
- }
-
-
- //3.获取token
- String token = request.getHeaders().getFirst("token");
-
- //4.判断token是否存在
- if(StringUtils.isBlank(token)){
- response.setStatusCode(HttpStatus.UNAUTHORIZED);
- return response.setComplete();
- }
-
- //5.判断token是否有效
- try {
- Claims claimsBody = AppJwtUtil.getClaimsBody(token);
- //是否是过期
- int result = AppJwtUtil.verifyToken(claimsBody);
- if(result == 1 || result == 2){
- response.setStatusCode(HttpStatus.UNAUTHORIZED);
- return response.setComplete();
- }
- }catch (Exception e){
- e.printStackTrace();
- response.setStatusCode(HttpStatus.UNAUTHORIZED);
- return response.setComplete();
- }
-
- //6.放行
- return chain.filter(exchange);
- }
-
- /**
- * 优先级设置 值越小 优先级越高
- * @return
- */
- @Override
- public int getOrder() {
- return 0;
- }
- }
测试:
启动user服务,继续访问其他微服务,会提示需要认证才能访问,这个时候需要在heads中设置设置token才能正常访问。
通过nginx来进行配置,功能如下
通过nginx的反向代理功能访问后台的网关资源
通过nginx的静态服务器功能访问前端静态页面
①:解压资料文件夹中的压缩包nginx-1.18.0.zip
路径cmd,键入nginx,启动
②:解压资料文件夹中的前端项目app-web.zip
③:配置nginx.conf文件
在nginx安装的conf目录下新建一个文件夹leadnews.conf,在当前文件夹中新建heima-leadnews-app.conf文件
heima-leadnews-app.conf配置如下:
-
- upstream heima-app-gateway{
- server localhost:51601;
- }
-
- server {
- listen 8801;
- location / {
- root D:/workspace/app-web/;
- index index.html;
- }
-
- location ~/app/(.*) {
- proxy_pass http://heima-app-gateway/$1;
- proxy_set_header HOST $host; # 不改变源请求头的值
- proxy_pass_request_body on; #开启获取请求体
- proxy_pass_request_headers on; #开启获取请求头
- proxy_set_header X-Real-IP $remote_addr; # 记录真实发出请求的客户端IP
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #记录代理信息
- }
- }
nginx.conf 把里面注释的内容和静态资源配置相关删除,引入heima-leadnews-app.conf文件加载
-
-
- #user nobody;
- worker_processes 1;
-
- events {
- worker_connections 1024;
- }
- http {
- include mime.types;
- default_type application/octet-stream;
- sendfile on;
- keepalive_timeout 65;
- # 引入自定义配置文件
- include leadnews.conf/*.conf;
- }
④ :启动nginx
在nginx安装包中使用命令提示符打开,输入命令nginx启动项目
可查看进程,检查nginx是否启动
重新加载配置文件:nginx -s reload
使用windows版本的nginx启动时遇到(1113: No mapping for the Unicode character exists in the target multi-byte code page)这个错误
把nginx的版本升高了,依旧报错
后来查阅发现是因为解压的路径里面包含有中文的缘故,只要把解压后的文件剪切到没有包含中文的目录即可解决问题
在运行reload的时候报错:CreateFile() "D:\DevelopCode\JavaCode\Springcloud\leadnews\nginx-1.18.0/logs/nginx.pid" failed (2: The system cannot find the file specified)
加pid的文件,然后里面没东西运行失败,就start nginx,计算机自己在pid加了东西,成功reload!
打开8801 报错:500 Internal Server Error
这个错误通常表示在Windows操作系统上,Nginx无法识别路径中含有非英文字符的文件或文件夹。这可能是因为Nginx默认将其配置文件和日志文件等保存在UTF-8编码下,而Windows默认的编码方式是ANSI。
要解决这个问题,可以尝试以下方法:
将Nginx配置文件及相关路径修改为英文字符,避免包含非英文字符的路径。
修改Nginx的配置文件编码为 ANSI :在Nginx的配置文件中,找到 http 块,并在该块中添加以下指令:
plaintext复制代码http {
# ...
charset utf-8;
charset_types text/plain text/css application/javascript application/json text/javascript;
charset utf-8 off;
# ...
}
保存并重新启动Nginx服务。
⑤:打开前端项目进行测试 -- > http://localhost:8801
用谷歌浏览器打开,调试移动端模式进行访问
文章布局展示
ap_article 文章基本信息表
ap_article_config 文章配置表
ap_article_content 文章内容表
三张表关系分析
查看当天资料文件夹,在数据库连接工具中执行leadnews_article.sql
ap_article文章表对应实体
-
- package com.heima.model.article.pojos;
-
- import com.baomidou.mybatisplus.annotation.IdType;
- import com.baomidou.mybatisplus.annotation.TableField;
- import com.baomidou.mybatisplus.annotation.TableId;
- import com.baomidou.mybatisplus.annotation.TableName;
- import lombok.Data;
-
- import java.io.Serializable;
- import java.util.Date;
-
- /**
- * <p>
- * 文章信息表,存储已发布的文章
- * </p>
- *
- * @author itheima
- */
-
- @Data
- @TableName("ap_article")
- public class ApArticle implements Serializable {
-
- @TableId(value = "id",type = IdType.ID_WORKER)
- private Long id;
-
-
- /**
- * 标题
- */
- private String title;
-
- /**
- * 作者id
- */
- @TableField("author_id")
- private Long authorId;
-
- /**
- * 作者名称
- */
- @TableField("author_name")
- private String authorName;
-
- /**
- * 频道id
- */
- @TableField("channel_id")
- private Integer channelId;
-
- /**
- * 频道名称
- */
- @TableField("channel_name")
- private String channelName;
-
- /**
- * 文章布局 0 无图文章 1 单图文章 2 多图文章
- */
- private Short layout;
-
- /**
- * 文章标记 0 普通文章 1 热点文章 2 置顶文章 3 精品文章 4 大V 文章
- */
- private Byte flag;
-
- /**
- * 文章封面图片 多张逗号分隔
- */
- private String images;
-
- /**
- * 标签
- */
- private String labels;
-
- /**
- * 点赞数量
- */
- private Integer likes;
-
- /**
- * 收藏数量
- */
- private Integer collection;
-
- /**
- * 评论数量
- */
- private Integer comment;
-
- /**
- * 阅读数量
- */
- private Integer views;
-
- /**
- * 省市
- */
- @TableField("province_id")
- private Integer provinceId;
-
- /**
- * 市区
- */
- @TableField("city_id")
- private Integer cityId;
-
- /**
- * 区县
- */
- @TableField("county_id")
- private Integer countyId;
-
- /**
- * 创建时间
- */
- @TableField("created_time")
- private Date createdTime;
-
- /**
- * 发布时间
- */
- @TableField("publish_time")
- private Date publishTime;
-
- /**
- * 同步状态
- */
- @TableField("sync_status")
- private Boolean syncStatus;
-
- /**
- * 来源
- */
- private Boolean origin;
-
- /**
- * 静态页面地址
- */
- @TableField("static_url")
- private String staticUrl;
- }
ap_article_config文章配置对应实体类
-
- package com.heima.model.article.pojos;
-
- import com.baomidou.mybatisplus.annotation.IdType;
- import com.baomidou.mybatisplus.annotation.TableField;
- import com.baomidou.mybatisplus.annotation.TableId;
- import com.baomidou.mybatisplus.annotation.TableName;
- import lombok.Data;
-
- import java.io.Serializable;
-
- /**
- * <p>
- * APP已发布文章配置表
- * </p>
- *
- * @author itheima
- */
-
- @Data
- @TableName("ap_article_config")
- public class ApArticleConfig implements Serializable {
-
- @TableId(value = "id",type = IdType.ID_WORKER)
- private Long id;
-
- /**
- * 文章id
- */
- @TableField("article_id")
- private Long articleId;
-
- /**
- * 是否可评论
- * true: 可以评论 1
- * false: 不可评论 0
- */
- @TableField("is_comment")
- private Boolean isComment;
-
- /**
- * 是否转发
- * true: 可以转发 1
- * false: 不可转发 0
- */
- @TableField("is_forward")
- private Boolean isForward;
-
- /**
- * 是否下架
- * true: 下架 1
- * false: 没有下架 0
- */
- @TableField("is_down")
- private Boolean isDown;
-
- /**
- * 是否已删除
- * true: 删除 1
- * false: 没有删除 0
- */
- @TableField("is_delete")
- private Boolean isDelete;
- }
ap_article_content 文章内容对应的实体类
-
- package com.heima.model.article.pojos;
-
- import com.baomidou.mybatisplus.annotation.IdType;
- import com.baomidou.mybatisplus.annotation.TableField;
- import com.baomidou.mybatisplus.annotation.TableId;
- import com.baomidou.mybatisplus.annotation.TableName;
- import lombok.Data;
-
- import java.io.Serializable;
-
- @Data
- @TableName("ap_article_content")
- public class ApArticleContent implements Serializable {
-
- @TableId(value = "id",type = IdType.ID_WORKER)
- private Long id;
-
- /**
- * 文章id
- */
- @TableField("article_id")
- private Long articleId;
-
- /**
- * 文章内容
- */
- private String content;
- }
1,在默认频道展示10条文章信息
2,可以切换频道查看不同种类文章
3,当用户下拉可以加载最新的文章(分页)本页文章列表中发布时间为最大的时间为依据
4,当用户上拉可以加载更多的文章信息(按照发布时间)本页文章列表中发布时间最小的时间为依据
5,如果是当前频道的首页,前端传递默认参数:
maxBehotTime:0(毫秒)
minBehotTime:20000000000000(毫秒)--->2063年
加载首页 | 加载更多 | 加载最新 | |
接口路径 | /api/v1/article/load | /api/v1/article/loadmore | /api/v1/article/loadnew |
请求方式 | POST | POST | POST |
参数 | ArticleHomeDto | ArticleHomeDto | ArticleHomeDto |
响应结果 | ResponseResult | ResponseResult | ResponseResult |
ArticleHomeDto
-
- package com.heima.model.article.dtos;
-
- import lombok.Data;
- import java.util.Date;
-
- @Data
- public class ArticleHomeDto {
- // 最大时间
- Date maxBehotTime;
- // 最小时间
- Date minBehotTime;
- // 分页size
- Integer size;
- // 频道ID
- String tag;
- }
注意:需要在heima-leadnews-service的pom文件夹中添加子模块信息,如下:
- <modules>
- <module>heima-leadnews-user</module>
- <module>heima-leadnews-article</module>
- </modules>
在idea中的maven中更新一下,如果工程还是灰色的,需要在重新添加文章微服务的pom文件,操作步骤如下:
需要在nacos中添加对应的配置
- spring:
- datasource:
- driver-class-name: com.mysql.jdbc.Driver
- url: jdbc:mysql://localhost:3306/leadnews_article?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
- username: root
- password: root
- # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
- mybatis-plus:
- mapper-locations: classpath*:mapper/*.xml
- # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
- type-aliases-package: com.heima.model.article.pojos
-
- package com.heima.article.controller.v1;
-
- import com.heima.model.article.dtos.ArticleHomeDto;
- import com.heima.model.common.dtos.ResponseResult;
- 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.RestController;
-
- @RestController
- @RequestMapping("/api/v1/article")
- public class ArticleHomeController {
-
-
- @PostMapping("/load")
- public ResponseResult load(@RequestBody ArticleHomeDto dto) {
- return null;
- }
-
- @PostMapping("/loadmore")
- public ResponseResult loadMore(@RequestBody ArticleHomeDto dto) {
- return null;
- }
-
- @PostMapping("/loadnew")
- public ResponseResult loadNew(@RequestBody ArticleHomeDto dto) {
- return null;
- }
- }
mybatisPlus对多表查询不太友好,所以用mybatis自定义mapper查询
-
- package com.heima.article.mapper;
-
- import com.baomidou.mybatisplus.core.mapper.BaseMapper;
- import com.heima.model.article.dtos.ArticleHomeDto;
- import com.heima.model.article.pojos.ApArticle;
- import org.apache.ibatis.annotations.Mapper;
- import org.apache.ibatis.annotations.Param;
-
- import java.util.List;
-
- @Mapper
- public interface ApArticleMapper extends BaseMapper<ApArticle> {
-
- public List<ApArticle> loadArticleList(@Param("dto") ArticleHomeDto dto, @Param("type") Short type);
-
- }
对应的映射文件
在resources中新建mapper/ApArticleMapper.xml 如下配置:
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.heima.article.mapper.ApArticleMapper">
-
- <resultMap id="resultMap" type="com.heima.model.article.pojos.ApArticle">
- <id column="id" property="id"/>
- <result column="title" property="title"/>
- <result column="author_id" property="authorId"/>
- <result column="author_name" property="authorName"/>
- <result column="channel_id" property="channelId"/>
- <result column="channel_name" property="channelName"/>
- <result column="layout" property="layout"/>
- <result column="flag" property="flag"/>
- <result column="images" property="images"/>
- <result column="labels" property="labels"/>
- <result column="likes" property="likes"/>
- <result column="collection" property="collection"/>
- <result column="comment" property="comment"/>
- <result column="views" property="views"/>
- <result column="province_id" property="provinceId"/>
- <result column="city_id" property="cityId"/>
- <result column="county_id" property="countyId"/>
- <result column="created_time" property="createdTime"/>
- <result column="publish_time" property="publishTime"/>
- <result column="sync_status" property="syncStatus"/>
- <result column="static_url" property="staticUrl"/>
- </resultMap>
- <select id="loadArticleList" resultMap="resultMap">
- SELECT
- aa.*
- FROM
- `ap_article` aa
- LEFT JOIN ap_article_config aac ON aa.id = aac.article_id
- <where>
- and aac.is_delete != 1
- and aac.is_down != 1
- <!-- loadmore -->
- <if test="type != null and type == 1">
- and aa.publish_time <![CDATA[<]]> #{dto.minBehotTime}
- </if>
- <if test="type != null and type == 2">
- and aa.publish_time <![CDATA[>]]> #{dto.maxBehotTime}
- </if>
- <if test="dto.tag != '__all__'">
- and aa.channel_id = #{dto.tag}
- </if>
- </where>
- order by aa.publish_time desc
- limit #{dto.size}
- </select>
-
- </mapper>
-
- package com.heima.article.service;
-
- import com.baomidou.mybatisplus.extension.service.IService;
- import com.heima.model.article.dtos.ArticleHomeDto;
- import com.heima.model.article.pojos.ApArticle;
- import com.heima.model.common.dtos.ResponseResult;
-
- import java.io.IOException;
-
- public interface ApArticleService extends IService<ApArticle> {
-
- /**
- * 根据参数加载文章列表
- * @param loadtype 1为加载更多 2为加载最新
- * @param dto
- * @return
- */
- ResponseResult load(Short loadtype, ArticleHomeDto dto);
-
- }
实现类:
-
- package com.heima.article.service.impl;
-
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import com.heima.article.mapper.ApArticleMapper;
- import com.heima.article.service.ApArticleService;
- import com.heima.common.constants.ArticleConstants;
- import com.heima.model.article.dtos.ArticleHomeDto;
-
- import com.heima.model.article.pojos.ApArticle;
- import com.heima.model.common.dtos.ResponseResult;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
-
- import java.util.Date;
- import java.util.List;
-
-
- @Service
- @Transactional
- @Slf4j
- public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {
-
- // 单页最大加载的数字
- private final static short MAX_PAGE_SIZE = 50;
-
- @Autowired
- private ApArticleMapper apArticleMapper;
-
- /**
- * 根据参数加载文章列表
- * @param loadtype 1为加载更多 2为加载最新
- * @param dto
- * @return
- */
- @Override
- public ResponseResult load(Short loadtype, ArticleHomeDto dto) {
- //1.校验参数
- Integer size = dto.getSize();
- if(size == null || size == 0){
- size = 10;
- }
- size = Math.min(size,MAX_PAGE_SIZE);
- dto.setSize(size);
-
- //类型参数检验
- if(!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_MORE)&&!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_NEW)){
- loadtype = ArticleConstants.LOADTYPE_LOAD_MORE;
- }
- //文章频道校验
- if(StringUtils.isEmpty(dto.getTag())){
- dto.setTag(ArticleConstants.DEFAULT_TAG);
- }
-
- //时间校验
- if(dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());
- if(dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());
- //2.查询数据
- List<ApArticle> apArticles = apArticleMapper.loadArticleList(dto, loadtype);
-
- //3.结果封装
- ResponseResult responseResult = ResponseResult.okResult(apArticles);
- return responseResult;
- }
-
- }
定义常量类
-
- package com.heima.common.constants;
-
- public class ArticleConstants {
- public static final Short LOADTYPE_LOAD_MORE = 1;
- public static final Short LOADTYPE_LOAD_NEW = 2;
- public static final String DEFAULT_TAG = "__all__";
-
- }
-
- package com.heima.article.controller.v1;
-
- import com.heima.article.service.ApArticleService;
- import com.heima.common.constants.ArticleConstants;
- import com.heima.model.article.dtos.ArticleHomeDto;
- import com.heima.model.common.dtos.ResponseResult;
- import org.springframework.beans.factory.annotation.Autowired;
- 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.RestController;
-
- @RestController
- @RequestMapping("/api/v1/article")
- public class ArticleHomeController {
-
-
- @Autowired
- private ApArticleService apArticleService;
-
- @PostMapping("/load")
- public ResponseResult load(@RequestBody ArticleHomeDto dto) {
- return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_MORE,dto);
- }
-
- @PostMapping("/loadmore")
- public ResponseResult loadMore(@RequestBody ArticleHomeDto dto) {
- return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_MORE,dto);
- }
-
- @PostMapping("/loadnew")
- public ResponseResult loadNew(@RequestBody ArticleHomeDto dto) {
- return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_NEW,dto);
- }
- }
第一:在app网关的微服务的nacos的配置中心添加文章微服务的路由,完整配置如下:
- spring:
- cloud:
- gateway:
- globalcors:
- cors-configurations:
- '[/**]': # 匹配所有请求
- allowedOrigins: "*" #跨域处理 允许所有的域
- allowedMethods: # 支持的方法
- - GET
- - POST
- - PUT
- - DELETE
- routes:
- # 用户微服务
- - id: user
- uri: lb://leadnews-user
- predicates:
- - Path=/user/**
- filters:
- - StripPrefix= 1
- # 文章微服务
- - id: article
- uri: lb://leadnews-article
- predicates:
- - Path=/article/**
- filters:
- - StripPrefix= 1
第二:启动nginx,直接使用前端项目测试,启动文章微服务,用户微服务、app网关微服务
FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言, 不是 像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。
常用的java模板引擎还有哪些?
Jsp、Freemarker、Thymeleaf 、Velocity 等。
1.Jsp 为 Servlet 专用,不能单独进行使用。 已淘汰
2.Thymeleaf 为新技术,功能较为强大,但是执行的效率比较低。
3.Velocity从2010年更新完 2.0 版本后,便没有在更新。Spring Boot 官方在 1.4 版本后对此也不在支持,虽然 Velocity 在 2017 年版本得到迭代,但为时已晚。
freemarker作为springmvc一种视图格式,默认情况下SpringMVC支持freemarker视图格式。
需要创建Spring Boot+Freemarker工程用于测试模板。
创建一个freemarker-demo 的测试工程专门用于freemarker的功能测试与模板的测试。
pom.xml如下
- <?xml version="1.0" encoding="UTF-8"?>
- <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>heima-leadnews-test</artifactId>
- <groupId>com.heima</groupId>
- <version>1.0-SNAPSHOT</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <artifactId>freemarker-demo</artifactId>
-
- <properties>
- <maven.compiler.source>8</maven.compiler.source>
- <maven.compiler.target>8</maven.compiler.target>
- </properties>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-freemarker</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- </dependency>
- <!-- lombok -->
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- </dependency>
-
- <!-- apache 对 java io 的封装工具库 -->
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-io</artifactId>
- <version>1.3.2</version>
- </dependency>
- </dependencies>
-
- </project>
配置application.yml
- server:
- port: 8881 #服务端口
- spring:
- application:
- name: freemarker-demo #指定服务名
- freemarker:
- cache: false #关闭模板缓存,方便测试
- settings:
- template_update_delay: 0 #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
- suffix: .ftl #指定Freemarker模板文件的后缀名
在freemarker的测试工程下创建模型类型用于测试
-
- package com.heima.freemarker.entity;
-
- import lombok.Data;
-
- import java.util.Date;
-
- @Data
- public class Student {
- private String name;//姓名
- private int age;//年龄
- private Date birthday;//生日
- private Float money;//钱包
- }
在resources下创建templates,此目录为freemarker的默认模板存放目录。
在templates下创建模板文件 01-basic.ftl ,模板中的插值表达式最终会被freemarker替换成具体的数据。
<code class="language-plaintext hljs"><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<b>普通文本 String 展示:</b><br><br>
Hello ${name} <br>
<hr>
<b>对象Student中的数据展示:</b><br/>
姓名:${stu.name}<br/>
年龄:${stu.age}
<hr>
</body>
</html></code>
创建Controller类,向Map中添加name,最后返回模板文件。
-
- package com.xuecheng.test.freemarker.controller;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.client.RestTemplate;
- import java.util.Map;
-
- @Controller
- public class HelloController {
-
- @GetMapping("/basic")
- public String test(Model model) {
-
- //1.纯文本形式的参数
- model.addAttribute("name", "freemarker");
- //2.实体类相关的参数
-
- Student student = new Student();
- student.setName("小明");
- student.setAge(18);
- model.addAttribute("stu", student);
-
- return "01-basic";
- }
- }
01-basic.ftl,使用插值表达式填充数据
<code class="language-plaintext hljs"><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<b>普通文本 String 展示:</b><br><br>
Hello ${name} <br>
<hr>
<b>对象Student中的数据展示:</b><br/>
姓名:${stu.name}<br/>
年龄:${stu.age}
<hr>
</body>
</html></code>
-
- package com.heima.freemarker;
-
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
-
- @SpringBootApplication
- public class FreemarkerDemotApplication {
- public static void main(String[] args) {
- SpringApplication.run(FreemarkerDemotApplication.class,args);
- }
- }
请求:http://localhost:8881/basic
<code class="language-plaintext hljs"><#--我是一个freemarker注释--></code>
<code class="language-plaintext hljs">Hello ${name}</code>
3、FTL指令:和HTML标记类似,名字前加#予以区分,Freemarker会解析标签中的表达式或逻辑。
<code class="language-plaintext hljs"><# >FTL指令</#> </code>
4、文本,仅文本信息,这些不是freemarker的注释、插值、FTL指令的内容会被freemarker忽略解析,直接输出内容。
<code class="language-plaintext hljs"><#--freemarker中的普通文本-->
我是一个普通的文本</code>
1、数据模型:
在HelloController中新增如下方法:
-
- @GetMapping("/list")
- public String list(Model model){
-
- //------------------------------------
- Student stu1 = new Student();
- stu1.setName("小强");
- stu1.setAge(18);
- stu1.setMoney(1000.86f);
- stu1.setBirthday(new Date());
-
- //小红对象模型数据
- Student stu2 = new Student();
- stu2.setName("小红");
- stu2.setMoney(200.1f);
- stu2.setAge(19);
-
- //将两个对象模型数据存放到List集合中
- List<Student> stus = new ArrayList<>();
- stus.add(stu1);
- stus.add(stu2);
-
- //向model中存放List集合数据
- model.addAttribute("stus",stus);
-
- //------------------------------------
-
- //创建Map数据
- HashMap<String,Student> stuMap = new HashMap<>();
- stuMap.put("stu1",stu1);
- stuMap.put("stu2",stu2);
- // 3.1 向model中存放Map数据
- model.addAttribute("stuMap", stuMap);
-
- return "02-list";
- }
2、模板:
在templates中新增02-list.ftl文件
<code class="language-plaintext hljs"><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<#-- list 数据的展示 -->
<b>展示list中的stu数据:</b>
<br>
<br>
<table>
<tr>
<td>序号</td>
<td>姓名</td>
<td>年龄</td>
<td>钱包</td>
</tr>
</table>
<hr>
<#-- Map 数据的展示 -->
<b>map数据的展示:</b>
<br/><br/>
<a href="###">方式一:通过map['keyname'].property</a><br/>
输出stu1的学生信息:<br/>
姓名:<br/>
年龄:<br/>
<br/>
<a href="###">方式二:通过map.keyname.property</a><br/>
输出stu2的学生信息:<br/>
姓名:<br/>
年龄:<br/>
<br/>
<a href="###">遍历map中两个学生信息:</a><br/>
<table>
<tr>
<td>序号</td>
<td>姓名</td>
<td>年龄</td>
<td>钱包</td>
</tr>
</table>
<hr>
</body>
</html></code>
实例代码:
<code class="language-plaintext hljs"><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<#-- list 数据的展示 -->
<b>展示list中的stu数据:</b>
<br>
<br>
<table>
<tr>
<td>序号</td>
<td>姓名</td>
<td>年龄</td>
<td>钱包</td>
</tr>
<#list stus as stu>
<tr>
<td>${stu_index+1}</td>
<td>${stu.name}</td>
<td>${stu.age}</td>
<td>${stu.money}</td>
</tr>
</#list>
</table>
<hr>
<#-- Map 数据的展示 -->
<b>map数据的展示:</b>
<br/><br/>
<a href="###">方式一:通过map['keyname'].property</a><br/>
输出stu1的学生信息:<br/>
姓名:${stuMap['stu1'].name}<br/>
年龄:${stuMap['stu1'].age}<br/>
<br/>
<a href="###">方式二:通过map.keyname.property</a><br/>
输出stu2的学生信息:<br/>
姓名:${stuMap.stu2.name}<br/>
年龄:${stuMap.stu2.age}<br/>
<br/>
<a href="###">遍历map中两个学生信息:</a><br/>
<table>
<tr>
<td>序号</td>
<td>姓名</td>
<td>年龄</td>
<td>钱包</td>
</tr>
<#list stuMap?keys as key >
<tr>
<td>${key_index}</td>
<td>${stuMap[key].name}</td>
<td>${stuMap[key].age}</td>
<td>${stuMap[key].money}</td>
</tr>
</#list>
</table>
<hr>
</body>
</html></code>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。