一,實現效果
登錄:
注冊:
博客列表
個人博客中心
博客詳情:
更新博客
編寫博客
二,數據庫的建立和連接
首先,需要建庫,需要兩個實體,一個是用戶,一個是博客,需要如下屬性,需要注意的是需要將密碼的變長字符創設置的長一些,因為之后會對用戶的密碼進行加密,該博客中密碼要64為.所以提前預留好空間.另外由于用戶和博客都是不是物理刪除,而是邏輯刪除,所以每一個實體都有delete_flag
-- 建表SQL
create database if not exists java_blog charset utf8mb4;use java_blog;
-- 用戶表
DROP TABLE IF EXISTS java_blog.user_info;
CREATE TABLE java_blog.user_info(`id` INT NOT NULL AUTO_INCREMENT,`user_name` VARCHAR ( 128 ) NOT NULL,`password` VARCHAR ( 128 ) NOT NULL,`github_url` VARCHAR ( 128 ) NULL,`delete_flag` TINYINT ( 4 ) NULL DEFAULT 0,`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now(),PRIMARY KEY ( id ),
UNIQUE INDEX user_name_UNIQUE ( user_name ASC )) ENGINE = INNODB DEFAULT CHARACTER
SET = utf8mb4 COMMENT = '用戶表';-- 博客表
drop table if exists java_blog.blog_info;
CREATE TABLE java_blog.blog_info (`id` INT NOT NULL AUTO_INCREMENT,`title` VARCHAR(200) NULL,`content` TEXT NULL,`user_id` INT(11) NULL,`delete_flag` TINYINT(4) NULL DEFAULT 0,`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now(),PRIMARY KEY (id))
ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '博客表';-- 新增用戶信息
insert into java_blog.user_info (user_name, password,github_url)values("zhangsan","123456","https://gitee.com/bubble-fish666/class-java45");
insert into java_blog.user_info (user_name, password,github_url)values("lisi","123456","https://gitee.com/bubble-fish666/class-java45");insert into java_blog.blog_info (title,content,user_id) values("第一篇博客","111我是博客正文我是博客正文我是博客正文",1);
insert into java_blog.blog_info (title,content,user_id) values("第二篇博客","222我是博客正文我是博客正文我是博客正文",2);
進行數據庫的配置,我們這里使用的是Mybatis-plus,所以需要引入Mybatis-plus依賴
<dependency><!--Mybatis-plus依賴--><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.5</version></dependency>
鏈接數據庫,配置Resource->application.yml文件,這里要更改自己創建的庫名,和自己數據庫的密碼,另外這里還配置了駝峰轉化 (起到數據庫中對象的屬性和java創建的類屬性相對應),數據庫的日志打印,和日志永久化和分割
#數據庫連接配置datasource:url: jdbc:mysql://127.0.0.1:3306/java_blog?characterEncoding=utf8&useSSL=falseusername: rootpassword: "****"driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:configuration:map-underscore-to-camel-case: true # 駝峰轉化log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 配置mybatis-plus打印日志logging:file:name: logger/spring-blog-loglogback: #日志分割rollingpolicy:file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%imax-file-size: 1MB
創建與數據庫元素對應的java類,其中?@TableId(value = "id",type = IdType.AUTO),告訴 MyBatis-Plus 當前字段是數據庫表的主鍵,指定數據庫中主鍵列的列名(如果字段名與列名一致,可省略).定義主鍵的生成策略為數據庫自增
@Data
public class BlogInfo {@TableId(value = "id",type = IdType.AUTO)private Integer id;private String title;private String content;private Integer userId;private int deleteFlag;private Date createTime;private Date updateTime;}
@Data
public class UserInfo {@TableId(value = "id",type = IdType.AUTO)private Integer id;private String userName;private String password;private String githubUrl;private int deleteFlag;private Date createTime;private Date updateTime;
}
之后創建Mapper接口,使用Mybatis-plus來操作數據庫,由于數據庫里面有兩個表,所以創建兩個與之相對應的接口,記得加上@Mapper注解
@Mapper
public interface BlogInfoMapper extends BaseMapper<BlogInfo> {
}
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
這個項目是基于Spring-Boot創建的,在創建Spring項目的時候,需要引入一些依賴,Spring Boot 提供的?Web 開發場景啟動器,mysql的驅動,lombok依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>
三, 統一結果
統一返回,在開發中通常需要統一格式返回,所以我們設置一個類,將每個方法返回的數據統一用這個類進行包裝,命名為Result,里面包含成功的返回,以及發生異常的返回.這個類里面的屬性為{code,errMsg,data}狀態碼,正常為200,異常為-1.errMsg為錯誤信息,data通常放正常返回的數據內容,狀態碼我們放到一個枚舉類里面
@Getter
@AllArgsConstructor
public enum ResultStatusEnum {SUCCESS(200),FILE(-1);private int code;
}
import com.ABdolphin.blog.common.enums.ResultStatusEnum;
import lombok.Data;@Data
public class Result<T> {private int code;private String errMsg;private T data;public static <T> Result<T> ok (T data){Result<T> result=new Result();result.setCode(ResultStatusEnum.SUCCESS.getCode());result.setErrMsg(null);result.setData(data);return result;}public static <T> Result<T> fail (int code,String errMsg){Result<T> result=new Result();result.setCode(ResultStatusEnum.FILE.getCode());result.setErrMsg(errMsg);result.setCode(code);return result;}public static <T> Result<T> fail (String errMsg){Result<T> result=new Result();result.setCode(ResultStatusEnum.FILE.getCode());result.setErrMsg(errMsg);return result;}public static <T> Result<T> fail (String errMsg,T data){Result<T> result=new Result();result.setCode(ResultStatusEnum.FILE.getCode());result.setErrMsg(errMsg);result.setData(data);return result;}}
1, 統一返回
①該類引用ResponseBodyAdvice接口
②加上類注解:@ControllerAdvice
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Autowiredprivate ObjectMapper objectMapper;@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if (body instanceof Result){return body;}if (body instanceof String){return objectMapper.writeValueAsString(Result.ok(body));}return Result.ok(body);}
}
????????方法support方法相當于開關,當返回true的時候就會執行下面的方法進行封裝.如果返回false.當前轉換器會被跳過.如果代碼返回的是字符串的時候,通過這個類包裝后還是一個字符串,當原始返回值是 String 時, Spring 會優先使用 StringHttpMessageConverter(純文本轉換器),結果并不是一個JSON對象,即{code:200, data:"success", ...},就會報錯. 所以我們需要手動轉化一下,下面這個方法通過 ObjectMapper 將 Result 對象轉為 JSON 字符串(如 "{\"code\":200,\"data\":\"success\"}")
objectMapper.writeValueAsString(Result.ok(body));
2, 統一異常攔截
①類注解@ResponseBody: 將Result對象轉為JSON
②類注解@ControllerAdvice
③方法注解@ExceptionHandler
????????在這里可以攔截不同的異常,并返回不同的狀態碼,例如HandlerMethodValidationException和MethodArgumentNotValidException就是參數異常會報錯的類,我們在這里統一進行處理,并且返回401,這樣這個錯誤就直接反饋到了前端,也可以攔截自定義異常. 通常情況下,我會將未知的錯誤詳細的信息以堆棧的方式打印出來,已知的報錯只打印錯誤信息.
@Slf4j
@ResponseBody
@ControllerAdvice
public class ExceptionAdvice {@ExceptionHandler(Exception.class)public Result handle(Exception e ){log.error("發生異常,errMsg: ",e);return Result.fail(e.getMessage(),e);}@ExceptionHandler(BlogException.class)public Result handle(BlogException e ){log.error("發生異常,errMsg: {}",e.getErrMsg());return Result.fail(e.getErrMsg());}@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler({HandlerMethodValidationException.class, MethodArgumentNotValidException.class})public Result HandlerMethodValidationException(Exception e){log.error("參數不合法,e:{}",e.getMessage());return Result.fail("參數不合法",e.getMessage());}
}
創建一個自定義異常
創建一個自定義異常,就可以處理一些"功能執行異常"的錯誤,例如用戶名或密碼錯誤,插入失敗,token失效...
@Data
public class BlogException extends RuntimeException{private int code;public String errMsg;public BlogException() {}public BlogException(int code, String errMsg) {this.code = code;this.errMsg = errMsg;}public BlogException(String errMsg) {this.errMsg = errMsg;}
}
3, 攔截器
當用戶用訪問url訪問的時候,首先需要確認用戶是否登錄,這時就會通過驗證是否有token,且token是否合法,有才來驗證用戶是否有權限訪問網頁,例如,用戶直接訪問博客列表的時候,由于用戶之前沒有登陸,沒有token,就會被攔截.從而跳轉到登錄界面,讓用戶進行登錄,攔截器部分在用戶登錄接口講完后還會進行詳細講解
四,搭建三層框架
之后我們來創建基本的框架,即Controller層,Service層,Mapper層(之前已經建過)
并標上相對應的注解,并注入相關依賴
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {@Resource(name = "userInfoService")private UserService userService;
}
public interface UserService {}@Slf4j
@Service
public class UserInfoService implements UserService { @Autowiredprivate UserInfoMapper userInfoMapper;@Autowiredprivate BlogInfoMapper blogInfoMapper;}
@RestController
@RequestMapping("/blog")
@Slf4j
public class BlogController {@Resource(name = "blogInfoService")private BlogService blogService;
}
public interface BlogService {}@Service
@Slf4j
public class BlogInfoService implements BlogService {@Autowiredprivate BlogInfoMapper blogInfoMapper;@Autowiredprivate UserInfoMapper userInfoMapper;
}
以上準備工作準備的差不多了,我們來正式寫第一個接口
五,登錄
登錄這里我們將會用到jwt技術,對密碼加鹽加密,把token傳到前端進行本地儲存
/user/login POST
[參數]
{
“UserName”: “xxx”,
“password”:”xxxxx”
}
[響應]
{
?????? “code”:200,
?????? “errMsg”: null,
?????? “data”:
????????{
? ? ? ? ? ? ? ? "id" :xx,
? ? ? ? ? ? ? ? "token":"xxxxxxx"
????????}
}
1,加鹽加密
原因:由于有些用戶的密碼會存儲在數據庫中,但是用戶密碼這種涉及用戶隱私的數據需要進行加密處理,加密的算法有很多,這里我們選用MD5,MD5算法可以將一個不論多長的字符串轉化成都加密成一個定長字符串.而且理論上加密的數據是不能進行解密的,但是由于相同的字符串通道MD5加密后生成的字符串是一樣的,所以網絡上出現了MD5解密軟件,但實際上它的原理是將你輸入的MD5加密后的字符串,與它的數據庫里的結果進行遍歷,直到找到與之對應的,并找到對應加密之前的字符串. 這就導致如果數據庫中只儲存比較簡單的加密后的密碼,就會叫輕易的被破解掉.所以為了應對這一問題, 將會將用戶的密碼加鹽,之后將整個字符串在進行MD5加密.
鹽是一串隨機生成的字符串,這樣極大地增加了密碼的復雜性,不會輕易的被破解掉,但由于鹽是隨機生成的,所以為了后續驗證用戶的密碼是否正確,所以也需要將鹽儲存起來,由此數據庫中的是密碼組成為[MD5[密碼+鹽]]+鹽. 用戶登錄的時候,驗證用戶密碼輸入的是否正確時,由于MD5無法解密的特性,我們無法得知用戶真實的密碼.但由于MD5的特性,相同的字符串加密之后的生成的字符串是一致的,所以將用戶數據庫中密碼字符串中的鹽提取出來.再將用戶輸入的密碼再次按照之前加密加鹽的順序,再次進行加密,并將加密后的字符串和數據庫中的字符串進行對比,如果字符串相等,則說明用戶輸入的正確,但是如果不一致,則說明用戶輸入錯誤.【其中密碼和鹽的順序,加密后的字符串和鹽的順序,均可以隨意調換,但需要注意的是,加密的時候順序要和驗證時加密的順序要一致】
我們使用UUID.randomUUID().toString()生成一個隨機字符串,由于原始生成的里面含有"-",所以我們將"-"用空字符串代替"". 然后進行加密加鹽,返回生成的字符串,驗證的時候,取出鹽,將輸入的密碼加密加鹽,并進行比較,返回一個boolean類型
public static String encrypt(String password){String salt = UUID.randomUUID().toString().replace("-","");String security = (DigestUtils.md5DigestAsHex((salt + password).getBytes(StandardCharsets.UTF_8)))+salt;return security;}public static boolean verify(String SQLSecurity , String password){String salt = SQLSecurity.substring(32, 64);String security = (DigestUtils.md5DigestAsHex((salt + password).getBytes(StandardCharsets.UTF_8)))+salt;return SQLSecurity.equals(security);}
2,jwt令牌技術
令牌技術是為了驗證用戶進行了登錄,用戶登錄之后就會又有這個令牌,別再訪問其他url的時候,由于擁有令牌就可以隨意的訪問,相當于申請了一個通行證,反之如果沒有令牌,用戶訪問URL的時候就會報401錯誤,并跳轉到登錄界面讓用戶進行登錄.
JWT分為三個部分:
header:包括令牌的類型,以及使用的哈希算法
payload(負載):存放一些有效信息,可以進行自定義
signature:此部分防止JWT內容被篡改,確保安全性
首先引入依賴
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred --><version>0.11.5</version><scope>runtime</scope></dependency>
首先生成一個Key,由于key有長度限制,所以直接采用以下算法,這里是指簽名的算法采用HS256
Keys.secretKeyFor(SignatureAlgorithm.HS256);
之后通過Base64進行編碼,生成一個字符串,這就是簽名. 之后將簽名解碼并用以下方法,生成key
Keys.hmacShaKeyFor()
之后就可以生成token了.其中在payload放入一些自定義的數據,例如用戶id,用戶名,亦可以設置token有效時間
解析token的時候,通過如下方法創建一個解析器
Jwts.parserBuilder().setSigningKey(key).build();
public class JwtUtil {private static final String encode="+CXy+rc7+2HBu7rvApWzzKwjONEZo2FEs6Uinpbo13I=";private static final SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(encode));private static final long expiration= 24*60*60*1000 ;public static String getToken(Map<String,Object> map){String token = Jwts.builder().setClaims(map).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + expiration)).signWith(key).compact();return token;}public static Claims parseToken(String token){if (!StringUtils.hasLength(token)){return null;}JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();Claims body =null;try {body=build.parseClaimsJws(token).getBody();}catch ( SignatureException e){throw new BlogException(HttpStatus.UNAUTHORIZED.value(), "簽名異常");}catch (ExpiredJwtException e){throw new BlogException(HttpStatus.UNAUTHORIZED.value(),"token超時");}catch (Exception e){throw new BlogException(HttpStatus.UNAUTHORIZED.value(),"token解析失敗");}return body;}public String getKey(){SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);String encode = Encoders.BASE64.encode(secretKey.getEncoded());return encode;}
3,登錄代碼
之后正式進入登錄的代碼,首先根據接口文檔,得知我們收到是是一個JSON字符串轉化成的對象,所以我們創建一個與之屬性對應的類,這個類是用來接收前端發來的數據. 后端收到前端發來的數據時,要對數據進行最基礎的校驗,例如是否為空,最大長度是否超過指定值,這時我們就會用到參數校驗的注解,在此之前也需要加上參數校驗的依賴. 由于返回也有多個數值,所以我們也將其分裝成一個類,之后返回時,直接返回這個類就可以
<dependency><!--參數校驗--><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
@Data
public class LoginParam {@Length(max = 20)@NotBlank(message = "用戶名不能為空")private String userName;@Length(max = 20,min = 4)@NotBlank(message = "密碼不能為空")private String password;
}
@Data
public class LoginResponse {private Integer id;private String token;
}
在相應的屬性上加上校驗注解后,如果發現前端傳過來的參數不合格,就會直接返回400狀態碼,直接就將錯誤拋回前端了,就不會繼續進入后端.執行Controller后,需要注意的是,當參數為一個對象,則需要加上@RequestBody,當這個對象里面使用的參數校驗的注解,在這里就需要寫上@Validated,使屬性上的校驗注解生效
#Controller
@RequestMapping("/login")public LoginResponse login(@Validated @RequestBody LoginParam param){log.info("開始執行login...");return userService.login(param);}
進入Service代碼
#Service@Overridepublic LoginResponse login(LoginParam param) {UserInfo userInfo = searchUserByName(param.getUserName());if (userInfo==null){throw new BlogException("該用戶不存在");}if (!Security.verify(userInfo.getPassword(),param.getPassword())){throw new BlogException("用戶名或密碼錯誤");}Map<String,Object> map=new HashMap<>();map.put("userId",userInfo.getId());map.put("userName",userInfo.getUserName());String token = JwtUtil.getToken(map);LoginResponse loginResponse=new LoginResponse();loginResponse.setId(userInfo.getId());loginResponse.setToken(token);return loginResponse;}private UserInfo searchUserByName(String userName){return userInfoMapper.selectOne(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getUserName, userName).eq(UserInfo::getDeleteFlag, 0));}
4,攔截器實現
在登錄接口寫完之后,我們就可以完成之前沒有寫完的攔截器了,攔截器要進行注冊和初始化
注冊:
①引用HandlerInterceptor接口
②類注解@Component,讓攔截器被掃描
③重寫preHandle方法
由于我們會將userid和token放到用戶本地儲存,之后通過前端代碼,讓每次發送請求的時候,都帶有userId和token的請求頭,這里就是通過獲得請求中的請求頭的信息,獲得userId和token,然后將token進行驗證.如果token中的信息為空,或者token中的userId信息與前端傳過來的信息不符,則返回狀態碼401,并返回false,表示被攔截. 返回true.表示通過
@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("user_header_token");String userId = request.getHeader("user_header_id");Claims claims = JwtUtil.parseToken(token);if (claims==null||!claims.get("userId").toString().equals(userId)){response.setStatus(HttpStatus.UNAUTHORIZED.value());return false;}return true;}
}
初始化
①引用WebMvcConfigurer接口
②類注解@Configuration
③把注冊的攔截器注入進來
④重寫addInterceptors方法,并定義攔截的路徑
@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/blog/**","/user/**").excludePathPatterns("/user/login","/user/register","/user/getCaptcha","/user/checkCaptcha");}
}
至此后端代碼完成,我們來實現前端代碼,基礎html就不在這里過多贅述,這里只主要講ajax里面的邏輯
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客登陸頁</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/login.css"></head><body><div class="nav"><img src="pic/logo.jpg" alt=""><span class="blog-title">我的博客系統</span><div class="space"></div><a class="nav-span" href="blog_list.html">主頁</a><a class="nav-span" href="blog_edit.html">寫博客</a></div><div class="container-login"><div class="login-dialog"><h3>登陸</h3><div class="row"><span>用戶名</span><input type="text" name="username" id="username"></div><div class="row"><span>密碼</span><input type="password" name="password" id="password"></div><div class="captcha"><input type="text" name="inputCaptcha" id="inputCaptcha"><img id="verificationCodeImg" src="/user/getCaptcha" style="cursor: pointer;" title="看不清?換一張" /></div><div class="row"><button id="submit" onclick="login()">登錄</button></div><div class="row"><a id="register" href="blog_register.html">注冊</a></div></div></div><script src="js/jquery.min.js"></script><script src="js/common.js"></script><script>...</script>
</body></html>
在省略號的地方我們開始寫邏輯,由于我們傳到后端的是一個JSON對象,所以這里需要以下代碼
①contentType:"application/json"
②data:JSON.stringify({})
之后我們需要將傳來的結果中的userId和token進行本地存儲
localStorage.setItem("名稱",數值);
$.ajax({type:"post",url:"user/login",contentType:"application/json",data:JSON.stringify ({"userName" : $("#username").val(),"password" : $("#password").val()}),success : function(result){if(result.code==200&&result.data!=null){// console.log("verify.code==200");let data=result.data;localStorage.setItem("user_id",data.id);localStorage.setItem("user_token",data.token);location.assign("blog_list.html?currentPage=1");}else{alert(result.errMsg);}}});
前端攔截器相對應的配置,即將本地儲存的兩個變量以請求頭的形式傳給后端,由于每個請求的請求頭里都要有這兩個變量,所以每次發送請求的時候都要進行設置,所以將這段代碼放到公共代碼區里.
用這個方法即,每次發送請求前都會執行
$(document).ajaxSend(function (e, xhr, opt) {...});
得到本地存儲的數據
localStorage.getItem("名稱");
設置為請求頭
?xhr.setRequestHeader("請求頭名稱",數值);
$(document).ajaxSend(function (e, xhr, opt) {console.log("進入ajaxSend");let token=localStorage.getItem("user_token");let userId=localStorage.getItem("user_id");xhr.setRequestHeader("user_header_token",token);xhr.setRequestHeader("user_header_id",userId);
});
六,驗證碼
/user/checkCaptcha POST
[參數]
{
“code”: “xxx”,
}
[響應]
{
?????? “code”:200,
?????? “errMsg”: null,
?????? “data”:? true
}
這里我們直接調用Hutool里面已經實現的,進行調用.
首先引入依賴:
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-captcha</artifactId><version>5.8.37</version></dependency>
HutoolController
@RequestMapping("/getCaptcha")public void getCaptcha(HttpServletResponse response, HttpSession session){userService.getCaptcha(response,session);}@RequestMapping("/checkCaptcha")public boolean checkCaptcha(@NotBlank String code, HttpSession session){return userService.checkCaptcha(code,session);}
Service里面涉及很多配置相關的變量,所以將其統一放到一個類里面方便管理,之后將生成的驗證碼放到session里面,方便后續驗證是否正確.之后我們將生成的驗證碼圖片,允許直接向 HTTP 響應體寫入二進制數據,返回到前端.并設置相關返回類型. 驗證的時候從session中獲得數據和前端傳來的進行對比,并驗證是否超時
session.setAttribute("名稱",數值);
防止緩存? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? response.setHeader("Pragma","No-cache");
public class Properties {public static final int width =100;public static final int height =35;public static final int overTime = 60*1000;
}
@Overridepublic void getCaptcha(HttpServletResponse response, HttpSession session) {LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(Properties.width, Properties.height);session.setAttribute("captcha_create_time",new Date());session.setAttribute("captcha_code",lineCaptcha.getCode());try {lineCaptcha.write(response.getOutputStream());response.setContentType("image/jpeg");response.setCharacterEncoding("UTF-8");//防止緩存response.setHeader("Pragma","No-cache");} catch (IOException e) {throw new RuntimeException(e);}log.info("驗證碼為: {}",lineCaptcha.getCode());}
@Overridepublic boolean checkCaptcha(String code, HttpSession session) {if(session==null){throw new BlogException("驗證碼不存在,請重新刷新");}String captcha_code = session.getAttribute("captcha_code").toString();Date captcha_create_time = (Date)session.getAttribute("captcha_create_time");if (captcha_create_time==null||(System.currentTimeMillis()-captcha_create_time.getTime())>Properties.overTime){throw new BlogException("驗證碼超時,請重試!");}return captcha_code.equalsIgnoreCase(code);}
前端代碼:
首先更改一下html中的驗證碼圖片的來源,為src="/user/getCaptcha",有同一個目錄下可直接這樣寫
<div class="captcha"><input type="text" name="inputCaptcha" id="inputCaptcha"><img id="verificationCodeImg" src="/user/getCaptcha" style="cursor: pointer;" title="看不清?換一張" />
</div>
$.ajax({type:"post",url:"user/checkCaptcha",data:{"code" : $("#inputCaptcha").val(),},success : function(result){if(result.code==200&&result.data!=null&&result.data==true){// console.log("驗證碼驗證通過");verify();//進行用戶名和密碼的校驗} else{alert("驗證碼錯誤!");}}});
?七,注冊
?/user/register POST
[參數]
{
"userName": "xxxx",
?"password": "xxxxx",
?"githubUrl":"xxxxx"選填
}
[響應]
{
?????? “code”:200,
?????? “errMsg”: null,
?????? “data”:? true
}
@Data
public class RegisterParam {@Length(max = 20)@NotBlank(message = "用戶名不能為空")private String userName;@Length(max = 20,min = 4)@NotBlank(message = "密碼不能為空")private String password;@URL(message = "必須是有效的網址格式(如 http://example.com)")private String githubUrl;
}
@RequestMapping("/register")public boolean register(@Validated @RequestBody RegisterParam param){log.info("開始執行register...");return userService.register(param);}
@Overridepublic boolean register(RegisterParam param) {UserInfo userInfo1 = searchUserByName(param.getUserName());if (userInfo1!=null){throw new BlogException("用戶名重復!");}UserInfo userInfo=new UserInfo();userInfo.setUserName(param.getUserName());userInfo.setPassword(Security.encrypt(param.getPassword()));if (param.getGithubUrl()!=null){userInfo.setGithubUrl(param.getGithubUrl());}int result = userInfoMapper.insert(userInfo);if (result<1){throw new BlogException("注冊失敗");}else {return true;}}
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客注冊頁</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/login.css"></head><body><div class="nav"><img src="pic/logo.jpg" alt=""><span class="blog-title">我的博客系統</span><div class="space"></div><a class="nav-span" href="blog_login.html">返回</a></div><div class="container-login"><div class="login-dialog" style="display: inline-block; width: 500px; height: 428px; "><h3>注冊</h3><div class="row" style=" height: 20px"></div><div class="row" style=" width: 320px;"><span>用戶名</span><input type="text" name="username" id="username" placeholder="必填"></div><div class="row" style=" width: 320px;"><span>github url</span><input type="url" placeholder="選填 " pattern="https?://.+" name="githubUrl" id="githubUrl" title="請輸入http://或https://開頭的網址"></div><div class="row" style=" width: 320px;"><span>密碼</span><input type="password" name="password" id="password" placeholder="必填"></div><div class="row" style=" width: 320px;"><span style="display: inline-block; width: 66px">確認密碼</span><input type="password" name="repeatPassword" id="repeatPassword" placeholder="必填"></div><div class="row" style="display: inline-block; width: 280px; height: 20px; display: flex; justify-content: flex-end;"><span style="display: inline-block; font-size: 12px; color: #999; text-align: right;">密碼長度最少4位,最長20位</span></div><div class="row"><button id="submit" onclick="registor()">提交</button></div></div></div><script src="js/jquery.min.js"></script><script src="js/common.js"></script><script>function verify() {if ($("#password").val() == $("#repeatPassword").val()) {return true;} else {return false;}}function registor() {if (!verify()) {alert("密碼不一致")return;}$.ajax({type: "post",url: "user/register",contentType: "application/json",data: JSON.stringify({"userName": $("#username").val(),"password": $("#password").val(),"githubUrl": $("#githubUrl").val()}),success: function (result) {if (result.code == 200 && result.data != null && result.data == true) {location.assign("blog_login.html");} else {alert(result.errMsg);}}});}</script>
</body></html>
八,博客列表
/blog/getList?currentPage=??? get
[參數]
{ }
[響應]
{
?????? "code": 200,
? ? ? ?"errMsg": null,
? ? ? "data": {
? ? ? ? "total": 9,
? ? ? ? "records": [
? ? ? ? ? ? {
? ? ? ? ? ? ? ? "id": 1,
? ? ? ? ? ? ? ? "title": "第一篇博客",
? ? ? ? ? ? ? ? "content": "111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正",
? ? ? ? ? ? ? ? "userId": 1,
? ? ? ? ? ? ? ? "author": "zhangsan",
? ? ? ? ? ? ? ? "updateTime": "2025-04-23 13:55:08"
? ? ? ? ? ? },
? ? ? ? ? ? {
? ? ? ? ? ? ? ?.....
? ? ? ? ? ? },
....
}
由于想要對列表進行分頁,所以需要給后端傳過去當前的頁面currentPage,想要分頁,還需要知道每一頁的博客數,以及查詢博客的時候的偏離量select* BlogInfo limit 10,5),所以在后端也需要一個類來存放這些信息,其中records來存放博客的具體信息,因此也需要定義一個對象
@Data
public class PageBlogListParam {private Integer currentPage=1;private Integer pageSize=5;private Integer offset;public Integer getOffset(){return (currentPage-1)*pageSize;}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageBlogListResponse <T>{private long total;private List<T> records;private PageBlogListParam request;
}
@Data
public class BlogListResponse {private Integer id;private String title;private String content;private Integer userId;private String author;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date updateTime;public String getContent() {if (content.length()>=50){return content.substring(0,50);}else {return content;}}
}
對時間格式化
?@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
Controller
因為這里的參數并不是從前端過來的全部傳過來的,而是只穿來一個單一的參數,所以這里并不需要寫備注@RequestBody
@RequestMapping("/getList")public PageBlogListResponse<BlogListResponse> getBlogList(PageBlogListParam param){log.info("開始執行getListController...");return blogService.getBlogList(param);}
?想要使用Mybatis-plus查詢數據的總數
blogInfoMapper.selectCount(...);
last() 方法在 MyBatis-Plus 中用于?直接拼接 SQL 片段?到生成的 SQL 語句末尾
.last("LIMIT " + param.getOffset() + "," + param.getPageSize()));
將查詢的數據結果的對象全部轉化成擁有相同元素的對象,可以使用for循環,或者使用foreach,這里使用另一種方法
①blogInfos.stream()
?將?List<BlogInfo>
?轉換為?Stream<BlogInfo>
,以便進行流式處理
②.map(blogInfo -> { ... })
?對每個?BlogInfo
?對象進行轉換:
③collect(Collectors.toList())
將?Stream<BlogListResponse>
?重新收集為?List<BlogListResponse>
,得到最終結果。
@Overridepublic PageBlogListResponse<BlogListResponse> getBlogList(PageBlogListParam param) {log.info("開始執行getListService...");// 先查詢總數Long total = BlogListCount();// 再查詢分頁數據List<BlogInfo> blogInfos = blogInfoMapper.selectList(new LambdaQueryWrapper<BlogInfo>().eq(BlogInfo::getDeleteFlag, 0).last("LIMIT " + param.getOffset() + "," + param.getPageSize()));//轉化List<BlogListResponse> collect=conver(blogInfos);return new PageBlogListResponse<BlogListResponse>(total,collect,param);}private long BlogListCount(){return blogInfoMapper.selectCount(new LambdaQueryWrapper<BlogInfo>().eq(BlogInfo::getDeleteFlag, 0));}private List<BlogListResponse> conver (List<BlogInfo> blogInfos ){return blogInfos.stream().map(blogInfo -> {BlogListResponse response = BeanConver.trans(blogInfo);String userName = userInfoMapper.selectOne(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getId, blogInfo.getUserId())).getUserName();response.setAuthor(userName);return response;}).collect(Collectors.toList());}
前端
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客列表頁</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/list.css"><link rel="stylesheet" href="css/bootstrap.min.css"></head><body><div class="nav"><img src="pic/logo.jpg" alt=""><span class="blog-title">我的博客系統</span><div class="space"></div><a class="nav-span" href="blog_list.html">主頁</a><a class="nav-span" href="blog_edit.html">寫博客</a><a class="nav-span" href="#" onclick="logout()">退出</a></div><div class="container"><div class="left"><div class="card"><img src="pic/doge.jpg" alt=""><span style="display: block; color:gray ; text-align: center">個人資料</span><h3></h3><a class="GitHub" href="#">GitHub 地址</a><div class="row" style="display: block; align-items: center;"><a class="nav-span" href="blog_personal_list.html?currentPage=1">文章</a><span class="blogCount" style=" padding-left: 10px;"></span></div></div></div><div class="right"></div><div class="demo" style="background-color: transparent; "><ul id="pageContainer" class="pagination justify-content-center"></ul></div></div><script src="js/jquery.min.js"></script><script src="js/common.js"></script><script src="js/jq-paginator.js"></script><script>//顯示用戶信息var userUrl = "/user/getUserInfo?userId=" + localStorage.getItem("user_id");getUserInfo(userUrl);//顯示博客列表getBlogList();function getBlogList() {$.ajax({type: "get",url: "/blog/getList" + location.search,success: function (result) {if (result != null && result.code == 200 && result.data != null) {let finalHtml = "";let blogs = result.data.records;for (var blog of blogs) {finalHtml += '<div class="blog">';finalHtml += '<div class="title">' + blog.title + '</div>';finalHtml += '<div class="date">';finalHtml += '<apen class="date-time">' + blog.updateTime + '</apen>';finalHtml += '<apen class="author-name">' + blog.author + '</apen>';finalHtml += '</div>';finalHtml += '<div class="desc">' + blog.content + '</div>';finalHtml += '<a class="detail" href="blog_detail.html?blogId=' + blog.id + '">查看全文>></a>';finalHtml += '</div>';}$(".right").html(finalHtml);//翻頁信息$("#pageContainer").jqPaginator({totalCounts: result.data.total, //總記錄數pageSize: 5, //每頁的個數visiblePages: 5, //可視頁數currentPage: result.data.request.currentPage, //當前頁碼first: '<li class="page-item"><a class="page-link">first</a></li>',prev: '<li class="page-item"><a class="page-link" href="javascript:void(0);">up<\/a><\/li>',next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">down<\/a><\/li>',last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">last<\/a><\/li>',page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}<\/a><\/li>',//頁面初始化和頁碼點擊時都會執行onPageChange: function (page, type) {console.log("第" + page + "頁, 類型:" + type);if (type == "change") {location.href = "blog_list.html?currentPage=" + page;}}});}}});}</script>
</body></html>
?需要注意的是,下面兩者要對應上
九,獲得個人博客列表
獲得個人博客列表的程序,只是多了一個作者本人的查詢條件,這里就可以直接從請求頭中獲取信息
@Overridepublic PageBlogListResponse<BlogListResponse> getPersonalList(PageBlogListParam param, HttpServletRequest request) {Long total = BlogListCount();List<BlogInfo> blogInfos = blogInfoMapper.selectList(new LambdaQueryWrapper<BlogInfo>().eq(BlogInfo::getDeleteFlag, 0).eq(BlogInfo::getUserId,request.getHeader("user_header_id")).last("LIMIT " + param.getOffset() + "," + param.getPageSize()));List<BlogListResponse> collect=conver(blogInfos);return new PageBlogListResponse<BlogListResponse>(total,collect,param);}
個人列表的前端代碼與博客列表頁是相似的,這里就不多進行贅述了
十,獲得博客細節
/blog/getBlogDetail?blogId=???get
[參數]
{?}
[響應]
{
? ? "code": 200,
? ? "errMsg": null,
? ? "data": {
? ? ? ? "id": 1,
? ? ? ? "title": "第一篇博客",
? ? ? ? "content": "111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正",
? ? ? ? "userId": 1,
? ? ? ? "updateTime": "2025-04-23 13:55:08"
? ? }
}
由于返回的是完整的博客,所以這個返回類就要和之前的列表類區分開來
@Data
public class BlogDetailResponse {private Integer id;private String title;private String content;private Integer userId;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date updateTime;
}
@Overridepublic BlogDetailResponse getBlogDetail(Integer blogId) {log.info("開始執行getBlogDetailService...");BlogInfo blogInfo = blogInfoMapper.selectOne(new LambdaQueryWrapper<BlogInfo>().eq(BlogInfo::getId, blogId).eq(BlogInfo::getDeleteFlag, 0));if (blogInfo==null){throw new BlogException("查詢不到該博客,Id為:[ "+blogId+" ]的博客不存在");}return BeanConver.trans1(blogInfo);}public static BlogDetailResponse trans1 (BlogInfo blogInfo){BlogDetailResponse blogDetailResponse=new BlogDetailResponse();BeanUtils.copyProperties(blogInfo,blogDetailResponse);return blogDetailResponse;}
前端
這里使用的是markdown.由于這里從數據庫里面傳來的數據需要通過markdown來呈現出來,注意這里的detail是指id,所以要在html的語句上加id屬性,在此之前也要引入依賴
? ? <link rel="stylesheet" href="blog-editormd/css/editormd.css" />
? ? <script src="js/jquery.min.js"></script>
? ? <script src="blog-editormd/lib/marked.min.js"></script>
? ? <script src="blog-editormd/lib/prettify.min.js"></script>
? ? <script src="blog-editormd/editormd.js"></script>
? ? <script src="js/common.js"></script>
?editormd.markdownToHTML("detail", {
? ? ? ? ? ? ? ? ? ? ? ? ? ? markdown: blog.content,
? ? ? ? ? ? ? ? ? ? ? ? });
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客詳情頁</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/detail.css"></head><body><div class="nav"><img src="pic/logo.jpg" alt=""><span class="blog-title">我的博客系統</span><div class="space"></div><a class="nav-span" href="blog_list.html">主頁</a><a class="nav-span" href="blog_edit.html">寫博客</a><a class="nav-span" href="#" onclick="logout()">退出</a></div><div class="container"><div class="left"><div class="card"><img src="pic/doge.jpg" alt=""><span style="display: block; color:gray ; text-align: center">作者信息</span><h3></h3><a class="GitHub" href="#">GitHub 地址</a><div class="row" style="display: block; align-items: center;"><span>文章</span><span class="blogCount" style=" padding-left: 10px;"></span></div></div></div><div class="right"><div class="content"><div class="title"></div><div class="date"></div><div class="detail" id="detail" style="background-color: transparent;"></div><!-- <div class="operating"><button onclick="window.location.href='blog_update.html'">編輯</button><button onclick="deleteBlog()">刪除</button></div> --></div></div></div><!-- 引入 editor.md 的依賴 --><link rel="stylesheet" href="blog-editormd/css/editormd.css" /><script src="js/jquery.min.js"></script><script src="blog-editormd/lib/marked.min.js"></script><script src="blog-editormd/lib/prettify.min.js"></script><script src="blog-editormd/editormd.js"></script><script src="js/common.js"></script><script>//三步://1,引入依賴//2,更改ajax,其中里面的detail是指id//3,更改html加上 id=detail,背景顏色與父級顏色一致getBlogDetail();function getBlogDetail() {$.ajax({type: "get",url: "/blog/getBlogDetail" + location.search,success: function (result) {if (result != null && result.code == 200 && result.data != null) {let blog = result.data;$(".right .content .title").text(blog.title);$(".right .content .date").text(blog.updateTime);editormd.markdownToHTML("detail", {markdown: blog.content,});if (localStorage.getItem("user_id") == blog.userId) {let finalHtml = "";finalHtml += '<div class="operating">';finalHtml += '<button onclick="window.location.href=\'blog_update.html' + location.search + '\'">編輯</button>'finalHtml += '<button onclick="deleteBlog()">刪除</button>';finalHtml += '</div>';$(".content").append(finalHtml);}}}});}</script>
</body></html>
十一,添加圖書
//blog/add? post
[參數]
{
????????????????????"userId": "xxx"
? ? ? ? ? ? ? ? ? ? "title": "xxxx",
? ? ? ? ? ? ? ? ? ? "content": "xxx"
?}
[響應]
{
? ? "code": 200,
? ? "errMsg": null,
? ? "data": true
}
首先,前端傳到后端是一個JSON字符串,所以要設置一個與之對應的類來接收
@Data
public class AddBlogParam {@NotNullprivate Integer userId;@NotBlank(message = "標題不能為空")@Length(max = 25)private String title;@NotBlank(message = "內容不能為空")private String content;
}
@Overridepublic boolean add(AddBlogParam param) {BlogInfo blogInfo=new BlogInfo();blogInfo.setTitle(param.getTitle());blogInfo.setUserId(param.getUserId());blogInfo.setContent(param.getContent());int result= blogInfoMapper.insert(blogInfo);if (result>0){return true;}else {throw new BlogException("插入失敗,請聯系管理員");}}
前端
由于要在前端顯示出來markdown,所以html要加入markdown 插件 html代碼,前端獲取博客userId的時候,直接從本地存儲獲得
CTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客編輯頁</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/edit.css"><link rel="stylesheet" href="blog-editormd/css/editormd.css" /></head><body><div class="nav"><img src="pic/logo.jpg" alt=""><span class="blog-title">我的博客系統</span><div class="space"></div><a class="nav-span" href="blog_list.html">主頁</a><a class="nav-span" href="blog_edit.html">寫博客</a><a class="nav-span" href="#" onclick="logout()">退出</a></div><div class="content-edit"><div class="push"><input type="text" name="" id="title"><input type="button" value="發布文章" id="submit" onclick="submit()"></div><!-- markdown 插件 html代碼 --><div id="editor"><textarea style="display:none;" id="content" name="content">##在這里寫下一篇博客</textarea></div></div><script src="js/jquery.min.js"></script><script src="blog-editormd/editormd.min.js"></script><script src="js/common.js"></script><script type="text/javascript">$(function () {var editor = editormd("editor", {width: "100%",height: "550px",path: "blog-editormd/lib/"});});function submit() {$.ajax({type: "post",url: "/blog/add",contentType: "application/json",data: JSON.stringify({"userId": localStorage.getItem("user_id"),"title": $("#title").val(),"content": $("#content").val()}),success: function (result) {if (result != null && result.code == 200 && result.data != null && result.data == true) {alert("發布成功");location.assign("blog_personal_list.html?currentPage=1");} else {alert(result.errMsg);}}});}</script>
</body></html>
十二,更新博客
1,顯示博客內容
/blog/getBlogDetail?blogId=#? get
[參數]
{??}
[響應]
{
????????"id": 1,
? ? ? ? "title": "第一篇博客",
? ? ? ? "content": "111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正",
? ? ? ? "userId": 1,
? ? ? ? "updateTime": "2025-04-23 13:55:08"
}
后端代碼之前已經寫過了,前端直接調用就可以了,前端代碼在后面一起呈現
2,修改博客
/blog/update? post
[參數]
{
????????????????????"id": "xxx"
? ? ? ? ? ? ? ? ? ? "title": "xxxx",
? ? ? ? ? ? ? ? ? ? "content": "xxx"
?}
[響應]
{?
????"code": 200,
? ? "errMsg": null,
? ? "data": true
}
@Data
public class UpdateBlogParam {@NotNull//blogIdprivate Integer id;@NotBlank(message = "標題不能為空")@Length(max = 25)private String title;@NotBlank(message = "內容不能為空")private String content;
}
@Overridepublic boolean update(UpdateBlogParam param) {if (!IsLegal(param.getId())){throw new BlogException("更改失敗,無此博客");}int result= blogInfoMapper.update(new LambdaUpdateWrapper<BlogInfo>().eq(BlogInfo::getId,param.getId()).set(BlogInfo::getContent,param.getContent()).set(BlogInfo::getTitle,param.getTitle()));if (result>0){return true;}else {throw new BlogException("更新錯誤失敗,請聯系管理員");}}private boolean IsLegal(Integer blogId){BlogInfo blogInfo = blogInfoMapper.selectOne(new LambdaQueryWrapper<BlogInfo>().eq(BlogInfo::getId, blogId).eq(BlogInfo::getDeleteFlag, 0));if (blogInfo==null){return false;}else {return true;}}
前端
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客編輯頁</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/edit.css"><link rel="stylesheet" href="blog-editormd/css/editormd.css" /></head><body><div class="nav"><img src="pic/logo.jpg" alt=""><span class="blog-title">我的博客系統</span><div class="space"></div><a class="nav-span" href="blog_list.html">主頁</a><a class="nav-span" href="blog_edit.html">寫博客</a><a class="nav-span" href="#" onclick="logout()">退出</a></div><div class="content-edit"><div class="push"><input type="hidden" id="blogId"><input type="text" id="title"><input type="button" value="更新文章" id="submit" onclick="submit()"></div><!-- markdown 插件 html代碼 --><div id="editor"><textarea style="display:none;" id="content">##在這里寫下一篇博客</textarea></div></div><script src="js/jquery.min.js"></script><script src="blog-editormd/editormd.min.js"></script><script src="js/common.js"></script><script type="text/javascript">getBlogInfo();function submit() {$.ajax({type: "post",url: "/blog/update",contentType: "application/json",data: JSON.stringify({"id": $("#blogId").val(),"title": $("#title").val(),"content": $("#content").val()}),success: function (result) {if (result != null && result.code == 200 && result.data != null && result.data == true) {alert("更新成功");location.assign("blog_detail.html" + location.search + "");} else {alert(result.errMsg);}}});}function getBlogInfo() {$.ajax({type: "get",url: "/blog/getBlogDetail" + location.search,success: function (result) {if (result != null && result.code == 200 && result.data != null) {let blog = result.data;$("#blogId").val(blog.id);$("#title").val(blog.title);$("#content").val(blog.content);editormd("editor", {width: "100%",height: "550px",path: "blog-editormd/lib/",onload: function () {this.watch();this.setMarkdown(blog.content);}});}}});}</script>
</body></html>
十三,刪除博客
/blog/delete?blogId=#? get
[參數]
{??}
[響應]
{?
????"code": 200,
? ? "errMsg": null,
? ? "data": true
}
@Overridepublic boolean delete(Integer blogId) {if (!IsLegal(blogId)){throw new BlogException("刪除失敗,無此博客");}int result= blogInfoMapper.update(new LambdaUpdateWrapper<BlogInfo>().eq(BlogInfo::getId,blogId).set(BlogInfo::getDeleteFlag,1));if (result>0){return true;}else {throw new BlogException("刪除失敗,請聯系管理員");}}
function deleteBlog() {if (!confirm("確認刪除?")) {return;}$.ajax({type: "get",url: "/blog/delete" + location.search,success: function (result) {if (result != null && result.code == 200 && result.data != null && result.data == true) {alert("刪除成功");location.assign("blog_list.html?currentPage=1")} else {alert(result.errMsg);}}});
十四,獲得用戶信息
/user/getUserInfo?userId=#? get
[參數]
{??}
[響應]
{?
?"code": 200,
? ? "errMsg": null,
? ? "data": {
? ? ? ? "id": 1,
? ? ? ? "userName": "zhangsan",
? ? ? ? "githubUrl": "https://gitee.com/bubble-fish666/class-java45",
? ? ? ? "blogCount": 4
? ? }
}
@Overridepublic UserInfoResponse getUserInfo(Integer userId) {UserInfo userInfo = searchUserById(userId);UserInfoResponse userInfoResponse = BeanConver.trans(userInfo);userInfoResponse.setBlogCount(blogCount(userId));return userInfoResponse;}private long blogCount(Integer userId){return blogInfoMapper.selectCount(new LambdaQueryWrapper<BlogInfo>().eq(BlogInfo::getUserId,userId).eq(BlogInfo::getDeleteFlag,0));}private UserInfo searchUserById(Integer userId){return userInfoMapper.selectOne(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getId, userId).eq(UserInfo::getDeleteFlag, 0));}
十五,獲得作者信息
/user/getAuthorInfo?blogId=#? get
[參數]
{??}
[響應]
{
? ? "code": 200,
? ? "errMsg": null,
? ? "data": {
? ? ? ? "id": 1,
? ? ? ? "userName": "zhangsan",
? ? ? ? "githubUrl": "https://gitee.com/bubble-fish666/class-java45",
? ? ? ? "blogCount": 4
? ? }
}
@Overridepublic UserInfoResponse getAuthorInfo(Integer blogId) {BlogInfo blogInfo = blogInfoMapper.selectOne(new LambdaQueryWrapper<BlogInfo>().eq(BlogInfo::getId, blogId).eq(BlogInfo::getDeleteFlag, 0));UserInfo userInfo = searchUserById(blogInfo.getUserId());UserInfoResponse userInfoResponse = BeanConver.trans(userInfo);userInfoResponse.setBlogCount(blogCount(userInfo.getId()));return userInfoResponse;}
前端:這兩個使用的代碼是一樣的,只是URL不同,所以可以把getUserInfo寫到公共代碼區
//顯示用戶信息var userUrl = "/user/getUserInfo?userId=" + localStorage.getItem("user_id");getUserInfo(userUrl);//顯示博客作者信息var userUrl = "/user/getAuthorInfo" + location.search;getUserInfo(userUrl);function getUserInfo(url){$.ajax({type:"get",url: url,success : function(result){if(result!=null&&result.code==200&result.data!=null){let data=result.data;$(".container .left .card h3").text(data.userName);$(".container .left .card .GitHub").attr("href",data.githubUrl);$(".container .left .card .row .blogCount").text(data.blogCount);}else{alert(result.errMsg);}}});
}
十六,退出
寫在公共代碼區,只需要把本地存儲的token和userId刪除即可
function logout(){if(!confirm("確認退出?")){return;}localStorage.removeItem("user_token");localStorage.removeItem("user_id");location.assign("blog_login.html");
}
最后補充一些前端的代碼
統一異常處理
$(document).ajaxError(function(event,xhr,options,exc){
?//寫各種錯誤的處理方法
});
$(document).ajaxError(function(event,xhr,options,exc){if(xhr.status==401){alert("用戶未登錄,請先登錄");location.href="blog_login.html";}if(xhr.status==400){alert("參數不合法");}
});
最后,很感謝你能看到這里,這就是博客系統的全部內容,希望這些內容可以對你有所幫助