SpringBoot学习
1.创建SpringBoot项目
点击文件->新建->spring Initializr
选择需要的依赖,我这里只选择了Spring Web
选择文件位置
点击完成,创建完毕
2.使用MyBatis
我们先在数据库里创建一个user表,表里的内容如下
在刚才的springboot项目下,引入相关依赖
后面要演示拦截器的例子,所以先不采用前后端分离,方便等下学习
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
创建实体类User.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String username;
private String password;
}
dao层:UserDao.java
@Repository
@Mapper
public interface UserDao {
User login(User user);
User getUserByUserName(String username);
List<User>getUserList();
Integer getTotal();
User getUserById(Integer id);
boolean addUser(User user);
boolean deleteUserById(Integer id);
}
service层
UserService.java
public interface UserService {
User login(User user);
User getUserByUserName(String username);
List<User> getUserList();
Integer getTotal();
User getUserById(Integer id);
Integer addUser(User user);
boolean deleteUserById(Integer id);
}
UserServiceImpl.java
@Service
@Slf4j
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public User login(User user){
try{
User res=userDao.login(user);
return res;
}catch (Exception e){
log.error(e.getMessage(),e);
return null;
}
}
@Override
public User getUserByUserName(String username) {
try{
User user=userDao.getUserByUserName(username);
return user;
}catch (Exception e){
log.error(e.getMessage(),e);
return null;
}
}
@Override
public List<User> getUserList() {
try{
List<User>list=userDao.getUserList();
return list;
}catch (Exception e){
log.error(e.getMessage(),e);
return Collections.emptyList();
}
}
@Override
public Integer getTotal() {
try{
Integer total=userDao.getTotal();
return total;
}catch (Exception e){
log.error(e.getMessage(),e);
return -1;
}
}
@Override
public User getUserById(Integer id) {
try{
User user=userDao.getUserById(id);
return user;
}catch (Exception e){
log.error(e.getMessage(),e);
return null;
}
}
@Override
public Integer addUser(User user) {
try{
userDao.addUser(user);
//返回主键id
return user.getId();
}catch (Exception e){
log.error(e.getMessage(),e);
return -1;
}
}
@Override
public boolean deleteUserById(Integer id) {
try{
return userDao.deleteUserById(id);
}catch (Exception e){
log.error(e.getMessage(),e);
return false;
}
}
}
控制层UserController.java
@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
//跳转到主页
@RequestMapping("/index")
public ModelAndView index(){
log.info("跳转到主页=============");
ModelAndView modelAndView=new ModelAndView();
modelAndView.setViewName("index");
return modelAndView;
}
//跳转到登录页面
@RequestMapping("/loginUI")
public ModelAndView loginUI(){
log.info("跳转到登录页面============");
ModelAndView modelAndView=new ModelAndView();
modelAndView.setViewName("login");
return modelAndView;
}
//登录操作
@RequestMapping("/login")
public ModelAndView login(User user){
log.info("进行登录操作===============");
ModelAndView modelAndView=new ModelAndView();
user=userService.login(user);
if(Objects.isNull(user)){
modelAndView.setViewName("login");
}else{
modelAndView.setViewName("index");
modelAndView.addObject("user",user);
}
return modelAndView;
}
}
application.yml
server:
port: 8080
spring:
thymeleaf:
prefix: classpath:/templates/
suffix: .html
mvc:
view:
static-path-pattern: /static/**
datasource:
username: root
password: 3fa4d180
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/young?useSSL=false&serverTimezone=UTC
mybatis:
mapper-locations: classpath:/mapper/*.xml
登录页面:login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<form action="/login" method="post">
<label>
用户名:<input type="text" name="username" placeholder="请输入用户名"/><br>
</label>
<label>
密码:<input type="password" name="password" placeholder="请输入密码"/><br>
</label>
<input type="submit" value="登录"/>
</form>
</body>
</html>
主页:index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>主页</title>
</head>
<body>
<h3>登录成功,进入主页</h3>
</body>
</html>
UserDao.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.young.demo12.dao.UserDao">
<select id="login" parameterType="com.young.demo12.entity.User" resultType="com.young.demo12.entity.User">
select *
from user
where username=#{username} and password=#{password}
</select>
<select id="getUserByUserName" parameterType="string" resultType="com.young.demo12.entity.User">
select *
from user
where username=#{username}
</select>
<select id="getUserList" resultType="com.young.demo12.entity.User">
select *
from user
</select>
<select id="getTotal" resultType="int">
select count(*)
from user
</select>
<insert id="addUser" parameterType="com.young.demo12.entity.User" useGeneratedKeys="true" keyProperty="id">
insert into
user(id,username,password)
values
(#{id},#{username},#{password})
</insert>
<delete id="deleteUserById" parameterType="int">
delete
from user
where id=#{id}
</delete>
</mapper>
完整文件目录如下:
运行项目
访问:
http://localhost:8080/loginUI
3.拦截器
刚才的项目中,即使我不登录,也可以正常访问首页,这就很不合理了,所以我们需要拦截器来进行拦截
LoginInterceptor.java
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handle)throws Exception{
Cookie[]cookies=request.getCookies();
//cookie为空时,返回false
if(Objects.isNull(cookies)){
return false;
}
String username=null;
for(Cookie cookie:cookies){
if(cookie.getName().equals(LoginConfig.USERCOOKIE)){
username=cookie.getValue();
break;
}
}
//cookie过期或不存在相应的cookie,返回false
if(username==null||username==""){
return false;
}
return true;
}
}
LoginConfig.java
@Configuration
public class LoginConfig implements WebMvcConfigurer {
public static String USERCOOKIE="username_cookie";
@Override
public void addInterceptors(InterceptorRegistry registry){
//设置拦截器,拦截index,放行loginUI,login
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/index")
.excludePathPatterns("/loginUI","/login");
}
}
修改UserController的login
//登录操作
@RequestMapping("/login")
public ModelAndView login(User user, HttpServletResponse response){
log.info("进行登录操作===============");
ModelAndView modelAndView=new ModelAndView();
user=userService.login(user);
if(Objects.isNull(user)){
modelAndView.setViewName("login");
}else{
//设置cookie
Cookie cookie=new Cookie(LoginConfig.USERCOOKIE,user.getUsername());
//cookie路径
cookie.setPath("/");
//cookie存活时间,设置为1h
cookie.setMaxAge(60*60);
response.addCookie(cookie);
modelAndView.setViewName("index");
modelAndView.addObject("user",user);
}
return modelAndView;
}
运行项目
直接访问主页,被拦截
进行登录
登录成功,跳转到主页
重新访问主页,访问成功,说明cookie生效,免密登录成功
参考文章:springboot拦截器
4.JDBCTemplate
我们在刚才的基础上,进一步整合JDBCTemplate框架,并用它实现增删改查
首先创建一个微信用户表
我们引入相关依赖,来整合JDBCTemplate
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<!--jdbcTemplate-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
对返回值进行封装
ResultVO.java,注意,这里一定要有get和set方法
public class ResultVO <T>{
private Integer code;
private String msg;
private T data;
public ResultVO(){
}
public ResultVO(Integer code,String msg){
this.code=code;
this.msg=msg;
this.data=null;
}
public ResultVO(Integer code,String msg,T data){
this.code=code;
this.msg=msg;
this.data=data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
返回值处理工具类
ResultVOUtil.java
public class ResultVOUtil {
public static ResultVO success(){
return new ResultVO<>(200,"操作成功");
}
public static ResultVO success(Object data){
return new ResultVO<>(200,"操作成功",data);
}
public static ResultVO error(Integer code,String msg){
return new ResultVO<>(code,msg);
}
}
微信用户实体类WxUser.java
@Data
public class WxUser {
private Integer id;
private String openId;
private String nickName;
private String avatarUrl;
@Override
public String toString(){
return "WxUser{id="+id+",openId="+openId+",nickName="+nickName+
",avatarUrl="+avatarUrl+"}";
}
}
dao层
WxUserDao.java
public interface WxUserDao {
Integer addWxUser(WxUser user);
WxUser getWxUserById(Integer id);
List<WxUser>getWxUserList();
Boolean deleteWxUser(Integer id);
}
WxUserDaoImpl.java
@Repository
public class WxUserDaoImpl implements WxUserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//插入并返回主键
@Override
public Integer addWxUser(WxUser user) {
//sql语句
String sql="insert into wxuser(openId,nickName,avatarUrl) values(?,?,?)";
// jdbcTemplate.update(sql,user.getOpenId(),user.getNickName(),user.getAvatarUrl());
//获取自增主键
KeyHolder holder=new GeneratedKeyHolder();
jdbcTemplate.update(connection->{
PreparedStatement ps=connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
ps.setString(1,user.getOpenId());
ps.setString(2,user.getNickName());
ps.setString(3,user.getAvatarUrl());
return ps;
},holder);
int id= Objects.requireNonNull(holder.getKey()).intValue();
return id;
}
@Override
public WxUser getWxUserById(Integer id) {
String sql="select * from wxuser where id=?";
List<WxUser>list=jdbcTemplate.query(sql,new Object[]{id},new BeanPropertyRowMapper<>(WxUser.class));
if(list.size()>0){
return list.get(0);
}else{
return null;
}
}
@Override
public List<WxUser> getWxUserList() {
String sql="select * from wxuser";
List<WxUser>list=jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(WxUser.class));
return list;
}
@Override
public Boolean deleteWxUser(Integer id) {
String sql="delete from wxuser where id=?";
int i= jdbcTemplate.update(sql,new Object[]{id});
if(i>0){
return true;
}else{
return false;
}
}
}
service层
WxUserService.java
public interface WxUserService {
ResultVO addWxUser(JSONObject jsonObject);
ResultVO getWxUserById(Integer id);
ResultVO getWxUserList();
ResultVO deleteWxUserById(JSONObject jsonObject);
}
WxUserServiceImpl.java
@Service
@Slf4j
public class WxUserServiceImpl implements WxUserService {
@Autowired
private WxUserDao wxUserDao;
@Override
public ResultVO addWxUser(JSONObject jsonObject) {
String openId=jsonObject.getString("openId");
String nickName=jsonObject.getString("nickName");
String avatarUrl=jsonObject.getString("avatarUrl");
WxUser wxUser=new WxUser();
wxUser.setOpenId(openId);
wxUser.setNickName(nickName);
wxUser.setAvatarUrl(avatarUrl);
try{
Integer id=wxUserDao.addWxUser(wxUser);
Map<String,Object>res=new HashMap<>();
res.put("id",id);
return ResultVOUtil.success(res);
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"添加微信用户失败");
}
}
@Override
public ResultVO getWxUserById(Integer id) {
try{
WxUser wxUser=wxUserDao.getWxUserById(id);
Map<String,Object>res=new HashMap<>();
res.put("wxUser",wxUser);
return ResultVOUtil.success(res);
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"获取微信用户详情失败");
}
}
@Override
public ResultVO getWxUserList() {
try{
List<WxUser>list=wxUserDao.getWxUserList();
Map<String,Object>res=new HashMap<>();
res.put("list",list);
return ResultVOUtil.success(res);
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"获取微信用户列表失败");
}
}
@Override
public ResultVO deleteWxUserById(JSONObject jsonObject) {
String idStr=jsonObject.getString("id");
int id;
try{
id=Integer.parseInt(idStr);
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"传递的参数类型有误");
}
try{
boolean flag=wxUserDao.deleteWxUser(id);
if(flag){
Map<String,Object>res=new HashMap<>();
res.put("deleteNum",1);
return ResultVOUtil.success(res);
}else{
return ResultVOUtil.error(500,"删除微信用户失败");
}
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"删除微信用户失败");
}
}
}
控制层
WxUserController.java
@Slf4j
@RestController
public class WxUserController {
@Autowired
private WxUserService wxUserService;
@RequestMapping(value = "/wxuser/addWxUser",method = RequestMethod.POST)
public ResultVO addWxUser(@RequestBody JSONObject jsonObject){
log.info("添加微信用户=================");
return wxUserService.addWxUser(jsonObject);
}
@RequestMapping(value = "/wxuser/getWxUserById",method = RequestMethod.GET)
public ResultVO getWxUserById(@RequestParam(value = "id",required = true)Integer id){
log.info("获取微信用户详情===============");
return wxUserService.getWxUserById(id);
}
@RequestMapping(value = "/wxuser/getWxUserList",method = RequestMethod.GET)
public ResultVO getWxUserList(){
log.info("获取微信用户列表===============");
return wxUserService.getWxUserList();
}
@RequestMapping(value = "/wxuser/deleteWxUser",method = RequestMethod.DELETE)
public ResultVO deleteWxUser(@RequestBody JSONObject jsonObject){
log.info("删除微信用户=================");
return wxUserService.deleteWxUserById(jsonObject);
}
}
修改LoginConfig,将微信用户操作放行
@Configuration
public class LoginConfig implements WebMvcConfigurer {
public static String USERCOOKIE="username_cookie";
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/index")
.excludePathPatterns("/loginUI","/login","/wxuser/**");
}
}
运行项目,打开postman
添加微信用户
获取微信用户详情
获取微信用户列表:
删除微信用户
参考文章:SpringBoot高级篇JdbcTemplate之数据插入使用姿势详解
4.JWT(json web token)
这里先插入微信小程序背景
在我们刚才的例子中,很明显,即使我没使用微信小程序,只要我调用相应的接口,无论我是不是该小程序的用户,我都可以进行相应的操作,这是不合理的,我们必须采取鉴权,但是使用什么手段鉴权?可能会有人想到用cookie,但是在手机端,是不存在cookie的,而且cookie其实是不安全的,可能会造成用户信息的泄露,而且一些网站会禁用cookie导致我们无法使用cookie进行鉴权。
接下来,于是我们使用JWT来进行鉴权。
因为这里只是演示,并没有真正的小程序,所有也就不写微信登录的接口了,刚才那个WxUserController里的接口也不鉴权了,我们假设微信用户可以进行和帖子相关的操作。
引入依赖:
<!--jwt依赖-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.2</version>
</dependency>
JWT工具类
public class JWTUtil {
private static final String SECRET="1q2w3e";
//创造token
public static String createTokeN(Map<String,String>payload){
//设置过期时间
Calendar calendar=Calendar.getInstance();
calendar.add(Calendar.DATE,7);
JWTCreator.Builder builder= JWT.create();
payload.forEach((k,v)->builder.withClaim(k,v));
return builder.withExpiresAt(calendar.getTime())
.sign(Algorithm.HMAC256(SECRET));
}
//解析token
public static DecodedJWT resolveToken(String token){
JWTVerifier jwtVerifier=JWT.require(Algorithm.HMAC256(SECRET)).build();
DecodedJWT decodedJWT=jwtVerifier.verify(token);
return decodedJWT;
}
}
我们增加一个登录的接口,因为只是一个例子,所有登录过程不规范,只是为了方便获取token
WxUserService
public interface WxUserService {
ResultVO addWxUser(JSONObject jsonObject);
ResultVO getWxUserById(Integer id);
ResultVO getWxUserList();
ResultVO deleteWxUserById(JSONObject jsonObject);
ResultVO login(JSONObject jsonObject);
}
WxUserServiceImpl
@Override
public ResultVO login(JSONObject jsonObject){
String openId=jsonObject.getString("openId");
try{
WxUser wxUser=wxUserDao.getWxUserByOpenId(openId);
if(Objects.isNull(wxUser)){
return ResultVOUtil.error(500,"该微信用户不存在");
}
Map<String,String>payload=new HashMap<>();
//将信息存入负载
payload.put("id",wxUser.getId()+"");
payload.put("openId",wxUser.getOpenId());
payload.put("nickName",wxUser.getNickName());
payload.put("avatarUrl",wxUser.getAvatarUrl());
payload.put("openId",wxUser.getOpenId());
//获取用户凭证
String token= JWTUtil.createTokeN(payload);
//将用户信息和凭证返回给前端
Map<String,Object>res=new HashMap<>();
res.put("token",token);
res.put("wxUser",wxUser);
return ResultVOUtil.success(res);
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"查找微信用户失败");
}
}
WxUserDao
public interface WxUserDao {
Integer addWxUser(WxUser user);
WxUser getWxUserById(Integer id);
List<WxUser>getWxUserList();
Boolean deleteWxUser(Integer id);
WxUser getWxUserByOpenId(String openId);
}
WxUserDaoImpl.java
@Repository
public class WxUserDaoImpl implements WxUserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//插入并返回主键
@Override
public Integer addWxUser(WxUser user) {
//sql语句
String sql="insert into wxuser(openId,nickName,avatarUrl) values(?,?,?)";
// jdbcTemplate.update(sql,user.getOpenId(),user.getNickName(),user.getAvatarUrl());
//获取自增主键
KeyHolder holder=new GeneratedKeyHolder();
jdbcTemplate.update(connection->{
PreparedStatement ps=connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
ps.setString(1,user.getOpenId());
ps.setString(2,user.getNickName());
ps.setString(3,user.getAvatarUrl());
return ps;
},holder);
int id= Objects.requireNonNull(holder.getKey()).intValue();
return id;
}
@Override
public WxUser getWxUserById(Integer id) {
String sql="select * from wxuser where id=?";
List<WxUser>list=jdbcTemplate.query(sql,new Object[]{id},new BeanPropertyRowMapper<>(WxUser.class));
if(list.size()>0){
return list.get(0);
}else{
return null;
}
}
@Override
public List<WxUser> getWxUserList() {
String sql="select * from wxuser";
List<WxUser>list=jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(WxUser.class));
return list;
}
@Override
public Boolean deleteWxUser(Integer id) {
String sql="delete from wxuser where id=?";
int i= jdbcTemplate.update(sql,new Object[]{id});
if(i>0){
return true;
}else{
return false;
}
}
@Override
public WxUser getWxUserByOpenId(String openId) {
String sql="select * from wxuser where openId=?";
List<WxUser>list=jdbcTemplate.query(sql,new Object[]{openId},new BeanPropertyRowMapper<>(WxUser.class));
if(list.size()>0){
return list.get(0);
}else{
return null;
}
}
}
WxUserController.java
@Override
public ResultVO login(JSONObject jsonObject){
String openId=jsonObject.getString("openId");
try{
WxUser wxUser=wxUserDao.getWxUserByOpenId(openId);
Map<String,Object>res=new HashMap<>();
res.put("wxUser",wxUser);
return ResultVOUtil.success(res);
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"查找微信用户失败");
}
}
帖子实体类
Article.java,这里的isDelete是用来判断帖子是否删除的,我们项目中,一般是不会直接将数据删除的,而是通过一个标识来判断数据是否有效
@Data
public class Article {
private Integer id;
private WxUser wxUser;
private String content;
private Integer isDelete;
}
dao层
@Repository
@Mapper
public interface ArticleDao {
boolean addArticle(Map<String,Object>map);
boolean deleteArticle(Map<String,Object>map);
Article getArticleById(Integer id);
Integer getTotal();
List<Article>getArticleList();
}
service层
ArticleService.java
public interface ArticleService {
ResultVO addArticle(JSONObject jsonObject);
ResultVO deleteArticle(JSONObject jsonObject);
ResultVO getArticleById(JSONObject jsonObject);
ResultVO getArticleList(JSONObject jsonObject);
}
ArticleServiceImpl.java
@Slf4j
@Service
public class ArticleServiceImpl implements ArticleService {
@Autowired
private ArticleDao articleDao;
@Override
public ResultVO addArticle(JSONObject jsonObject) {
//获取token和文章信息
String token=jsonObject.getString("token");
String content=jsonObject.getString("content");
String userId=jsonObject.getString("userId");
//鉴权
try{
DecodedJWT decodedJWT= JWTUtil.resolveToken(token);
}catch(AlgorithmMismatchException e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(501,"鉴权算法不一致");
}catch (SignatureVerificationException e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(502,"签名失效");
}catch (TokenExpiredException e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(503,"token已过期");
}catch (Exception e){
//返回不同的状态码,帮助前端判断鉴权失败原因
log.error(e.getMessage(),e);
return ResultVOUtil.error(504,"微信用户鉴权失败");
}
try{
Map<String,Object>map=new HashMap<>();
map.put("content",content);
map.put("userId",userId);
boolean flag=articleDao.addArticle(map);
if(flag){
Map<String,Object>res=new HashMap<>();
res.put("id",map.get("id"));
//返回帖子主键
return ResultVOUtil.success(res);
}else{
return ResultVOUtil.error(500,"发布帖子失败");
}
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"发布帖子数据库操作失败");
}
}
@Override
public ResultVO deleteArticle(JSONObject jsonObject) {
//获取token和文章信息
String token=jsonObject.getString("token");
String id=jsonObject.getString("id");
//鉴权
try{
DecodedJWT decodedJWT= JWTUtil.resolveToken(token);
}catch(AlgorithmMismatchException e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(501,"鉴权算法不一致");
}catch (SignatureVerificationException e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(502,"签名失效");
}catch (TokenExpiredException e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(503,"token已过期");
}catch (Exception e){
//返回不同的状态码,帮助前端判断鉴权失败原因
log.error(e.getMessage(),e);
return ResultVOUtil.error(504,"微信用户鉴权失败");
}
try{
Map<String,Object>map=new HashMap<>();
map.put("id",id);
boolean flag=articleDao.deleteArticle(map);
if(flag){
Map<String,Object>res=new HashMap<>();
res.put("deleteNum",1);
//返回帖子主键
return ResultVOUtil.success(res);
}else{
return ResultVOUtil.error(500,"删除帖子失败");
}
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"删除帖子数据库操作失败");
}
}
@Override
public ResultVO getArticleById(JSONObject jsonObject) {
//获取token和文章信息
String token=jsonObject.getString("token");
String idStr=jsonObject.getString("id");
//鉴权
try{
DecodedJWT decodedJWT= JWTUtil.resolveToken(token);
}catch(AlgorithmMismatchException e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(501,"鉴权算法不一致");
}catch (SignatureVerificationException e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(502,"签名失效");
}catch (TokenExpiredException e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(503,"token已过期");
}catch (Exception e){
//返回不同的状态码,帮助前端判断鉴权失败原因
log.error(e.getMessage(),e);
return ResultVOUtil.error(504,"微信用户鉴权失败");
}
int id;
try{
id=Integer.parseInt(idStr);
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"参数类型有误");
}
try{
Article article=articleDao.getArticleById(id);
Map<String,Object>res=new HashMap<>();
res.put("article",article);
return ResultVOUtil.success(res);
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"获取帖子详情失败");
}
}
@Override
public ResultVO getArticleList(JSONObject jsonObject) {
//获取token和文章信息
String token=jsonObject.getString("token");
//鉴权
try{
DecodedJWT decodedJWT= JWTUtil.resolveToken(token);
}catch(AlgorithmMismatchException e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(501,"鉴权算法不一致");
}catch (SignatureVerificationException e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(502,"签名失效");
}catch (TokenExpiredException e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(503,"token已过期");
}catch (Exception e){
//返回不同的状态码,帮助前端判断鉴权失败原因
log.error(e.getMessage(),e);
return ResultVOUtil.error(504,"微信用户鉴权失败");
}
try{
List<Article>list=articleDao.getArticleList();
Map<String,Object>res=new HashMap<>();
res.put("list",list);
return ResultVOUtil.success(list);
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"获取帖子列表数据库操作失败");
}
}
}
控制层 ArticleController.java
@Slf4j
@RestController
public class ArticleController {
@Autowired
private ArticleService articleService;
@RequestMapping(value = "/wxuser/addArticle",method = RequestMethod.POST)
public ResultVO addArticle(@RequestBody JSONObject jsonObject){
log.info("微信用户发布帖子=================");
return articleService.addArticle(jsonObject);
}
@RequestMapping(value = "/wxuser/deleteArticle",method = RequestMethod.DELETE)
public ResultVO deleteArticle(@RequestBody JSONObject jsonObject){
log.info("微信用户删除帖子=================");
return articleService.deleteArticle(jsonObject);
}
@RequestMapping(value = "/wxuser/getArticleById",method = RequestMethod.POST)
public ResultVO getArticleById(@RequestBody JSONObject jsonObject){
log.info("微信用户获取帖子详情=================");
return articleService.getArticleById(jsonObject);
}
@RequestMapping(value = "/wxuser/getArticleList",method = RequestMethod.POST)
public ResultVO getArticleList(@RequestBody JSONObject jsonObject){
log.info("微信用户获取帖子列表=================");
return articleService.getArticleList(jsonObject);
}
}
帖子表
ArticleDao.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.young.demo12.dao.ArticleDao">
<resultMap id="articleBean" type="com.young.demo12.entity.Article">
<id property="id" column="id"/>
<result property="content" column="content"/>
<result property="isDelete" column="is_delete"/>
<association property="wxUser" javaType="com.young.demo12.entity.WxUser">
<id property="id" column="wid"/>
<result property="openId" column="openId"/>
<result property="nickName" column="nickName"/>
<result property="avatarUrl" column="avatarUrl"/>
</association>
</resultMap>
<insert id="addArticle" parameterType="map" useGeneratedKeys="true" keyProperty="id">
insert into article(user_id,content,is_delete)
values
(#{userId},#{content},0)
</insert>
<update id="deleteArticle" parameterType="map">
update article
set is_delete=1
where id=#{id}
</update>
<select id="getArticleById" parameterType="int" resultMap="articleBean">
select a.id,a.content,
a.is_delete,w.id wid,
w.openId,w.nickName,
w.avatarUrl
from article a inner join wxuser w
on a.user_id=w.id
where a.is_delete=0
and a.id=#{id}
</select>
<select id="getTotal" resultType="int">
select count(*)
from article a inner join wxuser w
on a.user_id=w.id
where a.is_delete=0
</select>
<select id="getArticleList" resultMap="articleBean">
select a.id,a.content,
a.is_delete,w.id wid,
w.openId,w.nickName,
w.avatarUrl
from article a inner join wxuser w
on a.user_id=w.id
where a.is_delete=0
</select>
</mapper>
运行
因为刚才我们没有获取token,所以先调用login接口获取token
发布帖子
获取帖子详情
获取帖子列表
删除帖子
参考文章SpringBoot整合JWT
5.AOP
在刚才的例子中,我们发现一个问题,就是帖子操作前都要进行鉴权,鉴权逻辑是一样的,那么我们是不是可以把鉴权代码提取出来,可能有人想到用一个拦截器来拦截,这当然也可以,不过我要介绍的是另一种方法:AOP,aop是SpringBoot的两大核心,另一个核心是loc
我们先引入依赖
<!--aop依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
创建一个切入点类,一开始向通过前置通知鉴权,然后如果鉴权失败抛出错误,返回ResultVO来通知前端鉴权失败的原因,但是发现使用afterThrowing要进入方法后抛出错误才会执行,先这样吧,后边看看怎么改
@Aspect
@Component
@Slf4j
public class ArticleAop {
//切入点,待增强的方法
@Pointcut("execution(public * com.young.demo12.controller.ArticleController.*(..))")
//切入点签名
public void log(){
log.info("pointCut签名===========");
}
//前置通知
@Before("log()")
public void doBefore(JoinPoint jp)throws Throwable{
log.info("前置通知==================");
//获取请求
ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request=attributes.getRequest();
//获取请求头中的token
String token=request.getHeader("token");
//验证token
JWTUtil.resolveToken(token);
}
}
修改ArticleServiceImpl.java
@Slf4j
@Service
public class ArticleServiceImpl implements ArticleService {
@Autowired
private ArticleDao articleDao;
@Override
public ResultVO addArticle(JSONObject jsonObject) {
String content=jsonObject.getString("content");
String userId=jsonObject.getString("userId");
try{
Map<String,Object>map=new HashMap<>();
map.put("content",content);
map.put("userId",userId);
boolean flag=articleDao.addArticle(map);
if(flag){
Map<String,Object>res=new HashMap<>();
res.put("id",map.get("id"));
//返回帖子主键
return ResultVOUtil.success(res);
}else{
return ResultVOUtil.error(500,"发布帖子失败");
}
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"发布帖子数据库操作失败");
}
}
@Override
public ResultVO deleteArticle(JSONObject jsonObject) {
//获取token和文章信息
String token=jsonObject.getString("token");
String id=jsonObject.getString("id");
try{
Map<String,Object>map=new HashMap<>();
map.put("id",id);
boolean flag=articleDao.deleteArticle(map);
if(flag){
Map<String,Object>res=new HashMap<>();
res.put("deleteNum",1);
//返回帖子主键
return ResultVOUtil.success(res);
}else{
return ResultVOUtil.error(500,"删除帖子失败");
}
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"删除帖子数据库操作失败");
}
}
@Override
public ResultVO getArticleById(JSONObject jsonObject) {
//获取token和文章信息
String token=jsonObject.getString("token");
String idStr=jsonObject.getString("id");
int id;
try{
id=Integer.parseInt(idStr);
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"参数类型有误");
}
try{
Article article=articleDao.getArticleById(id);
Map<String,Object>res=new HashMap<>();
res.put("article",article);
return ResultVOUtil.success(res);
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"获取帖子详情失败");
}
}
@Override
public ResultVO getArticleList(JSONObject jsonObject) {
//获取token和文章信息
String token=jsonObject.getString("token");
try{
List<Article>list=articleDao.getArticleList();
Map<String,Object>res=new HashMap<>();
res.put("list",list);
return ResultVOUtil.success(list);
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"获取帖子列表数据库操作失败");
}
}
}
运行,现在我们不行要请求体上加上token,我们把token加在请求头上
然后发布帖子
获取帖子详情
获取帖子列表
删除帖子
我们观察控制台,有打印“前置通知”,说明确实有切入成功
参考文章:如何优雅地在SpringBoot中使用自定义注解,AOP切面统一打印出入参数
6.Redis
在上述情况中,我们每次获取帖子信息都要从数据库获取,这样做开销比较大,我们可以先把数据放入缓存中,然后先从缓存中获取,如果缓存中没有在从数据库获取,所有在这一节,我们引入Redis
引入依赖
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
RedisUtil工具类,因为这里没有设计hash、set等更高级的数据结构,所有我写这个工具类也没有考虑这些的存取
@Slf4j
@Component
public class RedisUtil {
@Autowired
private StringRedisTemplate redisTemplate;
private final String PRE_VALUE="article_";
public <K,V>void add(K key,V value){
try{
if(value!=null){
String valueStr= JSON.toJSONString(value);
redisTemplate.opsForValue()
.set(PRE_VALUE+key,valueStr);
}
}catch (Exception e){
log.error(e.getMessage(),e);
}
}
public <K,V>void add(K key,V value,long timeout){
try{
if(value!=null){
String valueStr=JSON.toJSONString(value);
redisTemplate.opsForValue()
.set(PRE_VALUE+key,valueStr,timeout, TimeUnit.SECONDS);
}
}catch (Exception e){
log.error(e.getMessage(),e);
}
}
public <K,V> V getObject(K key,Class<V>clazz){
String value=this.get(key);
V result=null;
if(!StringUtils.isEmpty(value)){
result=JSON.parseObject(value,clazz);
}
return result;
}
public <K,V>List<V> getList(K key,Class<V>clazz){
String value=this.get(key);
List<V>result= Collections.emptyList();
if(!StringUtils.isEmpty(value)){
result=JSON.parseArray(value,clazz);
}
return result;
}
public <K> String get(K key){
String value;
try{
value=redisTemplate.opsForValue()
.get(PRE_VALUE+key);
}catch (Exception e){
log.error(e.getMessage(),e);
throw new RuntimeException("从Redis获取失败");
}
return value;
}
public <K> void delete(K key){
redisTemplate.delete(PRE_VALUE+key);
}
public <K>boolean expire(K key,long timeout){
return redisTemplate.expire(PRE_VALUE+key,timeout, TimeUnit.SECONDS);
}
public <K> long getExpire(K key){
return redisTemplate.getExpire(PRE_VALUE+key);
}
}
修改ArticleServiceImpl.java
@Slf4j
@Service
public class ArticleServiceImpl implements ArticleService {
@Autowired
private ArticleDao articleDao;
@Autowired
private RedisUtil redisUtil;
@Autowired
private WxUserDao wxUserDao;
@Override
public ResultVO addArticle(JSONObject jsonObject) {
String content=jsonObject.getString("content");
String userId=jsonObject.getString("userId");
try{
Map<String,Object>map=new HashMap<>();
map.put("content",content);
map.put("userId",userId);
boolean flag=articleDao.addArticle(map);
if(flag){
Article article=new Article();
String idStr=String.valueOf(map.get("id"));
int id=Integer.parseInt(idStr);
article.setId(id);
article.setContent(content);
article.setIsDelete(0);
//获取微信用户
WxUser wxUser=wxUserDao.getWxUserById(Integer.parseInt(userId));
article.setWxUser(wxUser);
//添加到redis缓存中
redisUtil.add(article.getId(),article);
//更新缓存
resetCache();
Map<String,Object>res=new HashMap<>();
res.put("id",map.get("id"));
//返回帖子主键
return ResultVOUtil.success(res);
}else{
return ResultVOUtil.error(500,"发布帖子失败");
}
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"发布帖子数据库操作失败");
}
}
@Override
public ResultVO deleteArticle(JSONObject jsonObject) {
//获取token和文章信息
String token=jsonObject.getString("token");
String id=jsonObject.getString("id");
try{
Map<String,Object>map=new HashMap<>();
map.put("id",id);
boolean flag=articleDao.deleteArticle(map);
if(flag){
//删除缓存
redisUtil.delete(id);
//重载缓存
resetCache();
Map<String,Object>res=new HashMap<>();
res.put("deleteNum",1);
//返回帖子主键
return ResultVOUtil.success(res);
}else{
return ResultVOUtil.error(500,"删除帖子失败");
}
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"删除帖子数据库操作失败");
}
}
@Override
public ResultVO getArticleById(JSONObject jsonObject) {
//获取token和文章信息
String token=jsonObject.getString("token");
String idStr=jsonObject.getString("id");
int id;
try{
id=Integer.parseInt(idStr);
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"参数类型有误");
}
try{
//先从缓存中获取
Article article=redisUtil.getObject(id,Article.class);
if(!Objects.isNull(article)){
log.info("从缓存中获取==============");
Map<String,Object>res=new HashMap<>();
res.put("article",article);
return ResultVOUtil.success(res);
}
article=articleDao.getArticleById(id);
//添加到缓存中
redisUtil.add(article.getId(),article);
//重载缓存
resetCache();
Map<String,Object>res=new HashMap<>();
res.put("article",article);
return ResultVOUtil.success(res);
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"获取帖子详情失败");
}
}
@Override
public ResultVO getArticleList(JSONObject jsonObject) {
//获取token和文章信息
String token=jsonObject.getString("token");
try{
//先从缓存中获取
List<Article>list=redisUtil.getList("ALL",Article.class);
if(!Objects.isNull(redisUtil)&&list.size()>0){
log.info("从缓存中获取==============");
Map<String,Object>res=new HashMap<>();
res.put("list",list);
return ResultVOUtil.success(list);
}
list=articleDao.getArticleList();
//添加到缓存
redisUtil.add("ALL",list);
Map<String,Object>res=new HashMap<>();
res.put("list",list);
return ResultVOUtil.success(list);
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"获取帖子列表数据库操作失败");
}
}
//重置缓存
private void resetCache(){
try{
List<Article>list=articleDao.getArticleList();
redisUtil.add("ALL",list);
}catch (Exception e){
log.error(e.getMessage(),e);
}
}
}
修改application.yml
server:
port: 8080
spring:
redis:
host: 127.0.0.1
port: 6379
timeout: 300000
jedis:
pool:
max-active: 8
min-idle: 0
max-idle: 8
max-wait: -1
thymeleaf:
prefix: classpath:/templates/
suffix: .html
mvc:
view:
static-path-pattern: /static/**
datasource:
username: root
password: *******
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/young?useSSL=false&serverTimezone=UTC
mybatis:
mapper-locations: classpath:/mapper/*.xml
运行前先打开redis
运行
查看控制台
打开RedisDesktopManager,确实有刚才添加的缓存
7.配置多环境application.yml
在实际开发中,我们一般都是在本地环境测试,测试没问题再部署到服务器上,现在我们演示配置多个环境
修改applicatio.yml
spring:
profiles:
active: dev
application-dev.yml
server:
port: 8003
spring:
redis:
host: 127.0.0.1
port: 6379
timeout: 300000
jedis:
pool:
max-active: 8
min-idle: 0
max-idle: 8
max-wait: -1
thymeleaf:
prefix: classpath:/templates/
suffix: .html
mvc:
view:
static-path-pattern: /static/**
datasource:
username: root
password: 3fa4d180
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/young?useSSL=false&serverTimezone=UTC
mybatis:
mapper-locations: classpath:/mapper/*.xml
application-prod.yml
server:
port: 8003
spring:
redis:
host: 127.0.0.1
port: 6379
timeout: 300000
password:******
jedis:
pool:
max-active: 8
min-idle: 0
max-idle: 8
max-wait: -1
thymeleaf:
prefix: classpath:/templates/
suffix: .html
mvc:
view:
static-path-pattern: /static/**
datasource:
username: root
password: ******
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.137.128/young?useSSL=false&serverTimezone=UTC
mybatis:
mapper-locations: classpath:/mapper/*.xml
运行
本地没问题,我们修改application.yml
spring:
profiles:
active: prod
因为没钱买服务器,这里用虚拟机来模拟,先将项目打包
打开虚拟机,使用Xftp传输文件
打开navicat,同步一下数据库
运行jar包
先在虚拟机访问
本地访问
打开postman
8.HttpClient
在实际开发中,有时候我们会遇到一些要求,就是在项目中发送http请求获取内容,这里我仿照微信小程序登录的流程,来整合HttpClient
我们先创建另一个SpringBoot项目,用来模拟被请求的服务
WxLoginController.java
@RestController
public class WxLoginController {
@RequestMapping(value="/user/wxLogin",method = RequestMethod.GET)
public String wxLogin(@RequestParam(value = "code",required = true)String code){
System.out.println(code);
return UUID.randomUUID().toString();
}
}
在我们原本的项目里,导入依赖
<!--http-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
HttpClientUtil 工具类
public class HttpClientUtil {
//编码格式
private static final String ENCODING = "UTF-8";
//连接超时
private static final int CONNECT_TIMEOUT = 5000;
//请求连接超时
private static final int CONNECT_REQUEST_TIMEOUT = 5000;
//socket套接字超时
private static final int SOCKET_TIMEOUT = 5000;
public static ResultVO doGet(String url) throws Exception {
return doGet(url, null, null);
}
public static ResultVO doGet(String url, Map<String, String> params) throws Exception {
return doGet(url, null, params);
}
public static ResultVO doGet(String url, Map<String, String> headers, Map<String, String> params) throws Exception {
//创建HttpClient
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
//创建访问地址
URIBuilder uriBuilder = new URIBuilder(url);
if(params!=null&¶ms.size()>0){
params.forEach((k, v) -> uriBuilder.setParameter(k, v));
}
//创建http对象
HttpGet httpGet = new HttpGet(uriBuilder.build());
//配置
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(CONNECT_TIMEOUT)
.setConnectionRequestTimeout(CONNECT_REQUEST_TIMEOUT)
.setSocketTimeout(SOCKET_TIMEOUT).build();
httpGet.setConfig(requestConfig);
//设置请求头
packageHeader(headers, httpGet);
//创建httpResponse对象
CloseableHttpResponse httpResponse = null;
try {
//执行请求
return getHttpClientResult(httpResponse, httpClient, httpGet);
} finally {
//释放资源
release(httpResponse, httpClient);
}
}
public static ResultVO doPost(String url) throws Exception {
return doPost(url, null, null);
}
public static ResultVO doPost(String url, Map<String, String> params) throws Exception {
return doPost(url, null, params);
}
public static ResultVO doPost(String url, Map<String, String> headers, Map<String, String> params) throws Exception {
//创建httpClient
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
//创建http对象
HttpPost httpPost = new HttpPost(url);
//配置
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(CONNECT_TIMEOUT)
.setConnectionRequestTimeout(CONNECT_REQUEST_TIMEOUT)
.setSocketTimeout(SOCKET_TIMEOUT).build();
//设置请求头
packageHeader(headers, httpPost);
//封装请求参数
if (params != null) {
List<NameValuePair> list = new ArrayList<>();
params.forEach((k, v) -> list.add(new BasicNameValuePair(k, v)));
//设置请求体
httpPost.setEntity(new UrlEncodedFormEntity(list, ENCODING));
}
//获取响应结果
CloseableHttpResponse httpResponse = null;
try {
return getHttpClientResult(httpResponse, httpClient, httpPost);
} finally {
release(httpResponse, httpClient);
}
}
//封装头部
public static void packageHeader(Map<String, String> headers, HttpRequestBase httpMethod) {
if (headers != null) {
headers.forEach((k, v) -> httpMethod.setHeader(k, v));
}
}
//获取响应结果
public static ResultVO getHttpClientResult(CloseableHttpResponse httpResponse,CloseableHttpClient httpClient,HttpRequestBase httpMethod)throws Exception{
httpResponse=httpClient.execute(httpMethod);
if(httpResponse!=null&&httpResponse.getStatusLine()!=null){
String content="";
if(httpResponse.getEntity()!=null){
content= EntityUtils.toString(httpResponse.getEntity(),ENCODING);
}
return new ResultVO(httpResponse.getStatusLine().getStatusCode(),"获取成功",content);
}
return new ResultVO(HttpStatus.SC_INTERNAL_SERVER_ERROR,"请求失败");
}
//释放资源
public static void release(CloseableHttpResponse httpResponse,CloseableHttpClient httpClient)throws Exception{
if(httpResponse!=null){
httpResponse.close();
}
if(httpClient!=null){
httpClient.close();
}
}
}
修改WxUserController
@RequestMapping(value = "/wxuser/verify",method = RequestMethod.GET)
public ResultVO verify(@RequestParam(value = "code",required = true)String code){
log.info("发送请求获取openId==================");
try{
ResultVO resultVO=HttpClientUtil.doGet("http://localhost:8080/user/wxLogin?code="+code);
System.out.println(resultVO.getData());
return resultVO;
}catch (Exception e){
log.error(e.getMessage(),e);
return ResultVOUtil.error(500,"获取失败");
}
}
将两个项目都运行起来