Spring-博客系統項目

一,實現效果

登錄:

注冊:

博客列表

個人博客中心

博客詳情:

更新博客

編寫博客

二,數據庫的建立和連接

首先,需要建庫,需要兩個實體,一個是用戶,一個是博客,需要如下屬性,需要注意的是需要將密碼的變長字符創設置的長一些,因為之后會對用戶的密碼進行加密,該博客中密碼要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 + '">查看全文&gt;&gt;</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("參數不合法");}
});

最后,很感謝你能看到這里,這就是博客系統的全部內容,希望這些內容可以對你有所幫助

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/80219.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/80219.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/80219.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

依賴注入詳解與案例(前端篇)

依賴注入詳解與案例&#xff08;前端篇&#xff09; 一、依賴注入核心概念與前端價值 依賴注入&#xff08;Dependency Injection, DI&#xff09; 是一種通過外部容器管理組件/類間依賴關系的設計模式&#xff0c;其核心是控制反轉&#xff08;Inversion of Control, IoC&…

diy裝機成功錄

三天前&#xff0c;我正式開啟了這次裝機之旅&#xff0c;購入了一顆性能強勁的 i5-12400 CPU&#xff0c;一塊繪圖能力出色的 3060ti 顯卡&#xff0c;還有技嘉主板、高效散熱器、16G 內存條、2T 固態硬盤&#xff0c;以及氣派的機箱和風扇&#xff0c;滿心期待能親手打造一臺…

計算機三大主流操作系統的前世今生 - Linux|macOS|Windows

全文目錄 1 引言2 起源之路2.1 Linux 起源2.2 macOS 起源2.3 Windows 起源 3 綜合解析3.1 Linux系統綜合解析3.1.1 系統定義與核心架構3.1.2 發展歷程3.1.3 核心特點3.1.4 主流發行版3.1.5 應用場景 3.2 macOS系統綜合解析3.2.1 系統定義與核心架構3.2.2 發展歷程3.2.3 核心特點…

【AI智能推薦系統】第七篇:跨領域推薦系統的技術突破與應用場景

第七篇:跨領域推薦系統的技術突破與應用場景 提示語:?? “打破數據孤島,實現1+1>2的推薦效果!深度解析美團、亞馬遜如何用跨領域推薦技術實現業務協同,知識遷移核心技術全公開!” 目錄 跨領域推薦的商業價值跨領域推薦技術體系 2.1 基于共享表征的學習2.2 遷移學習…

R 語言科研繪圖 --- 桑基圖-匯總

在發表科研論文的過程中&#xff0c;科研繪圖是必不可少的&#xff0c;一張好看的圖形會是文章很大的加分項。 為了便于使用&#xff0c;本系列文章介紹的所有繪圖都已收錄到了 sciRplot 項目中&#xff0c;獲取方式&#xff1a; R 語言科研繪圖模板 --- sciRplothttps://mp.…

LintCode第485題-生成給定大小的數組,第220題-冰雹猜想,第235題-分解質因數

第485題 描述 給你一個大小size,生成一個元素從1 到 size的數組 樣例 1:輸入: size 4輸出: [1, 2, 3, 4]樣例解釋: 返回一個順序填充1到4的數組。樣例 2:輸入: size 1輸出: [1]樣例解釋: 返回一個順序填充1到1的數組 代碼如下: public class Solution { /** * param s…

Pandas:數據處理與分析

目錄 一、Pandas 簡介 二、Pandas 的安裝與導入 三、Pandas 的核心數據結構 &#xff08;一&#xff09;Series &#xff08;二&#xff09;DataFrame 四、Pandas 數據讀取與寫入 &#xff08;一&#xff09;讀取數據 &#xff08;二&#xff09;寫入數據 五、數據清洗…

Linux云計算訓練營筆記day05(Rocky Linux中的命令:管道操作 |、wc、find、vim)

管道操作 | 作用: 將前面命令的輸出&#xff0c;傳遞給后面命令&#xff0c;作為后面命令的參數 head -3 /etc/passwd | tail -1 取第三行 head -8 /etc/passwd | tail -3 | cat -n 取6 7 8行 ifconfig | head -2 | tail -1 只查看IP地址 ifconfig | grep 192 過濾192的ip…

動態規劃:最長遞增子序列

給定一個數組&#xff0c;求最長遞增子序列的長度,就是要求我們求出一個序列中最長的上升子序列的長度&#xff0c;最長上升子序列的定義就是從原序列中按照孫旭去除一些數字&#xff0c;這些數字是逐漸增大的。 *定義dp[i]表示以第i個元素結尾的最長上升子序列的長度。 *初始…

湖北理元理律師事務所:債務優化如何實現還款與生活的平衡?

債務壓力往往讓債務人陷入“還款還是生存”的兩難選擇。湖北理元理律師事務所通過案例實踐發現&#xff0c;科學規劃的核心在于平衡法律義務與基本生活保障&#xff0c;而非單純追求債務縮減。本文結合實務經驗&#xff0c;解析債務優化的可行路徑。 剛性需求優先&#xff1a;…

重力場模型、球諧函數以及重力異常

地球重力場有兩種表達方法&#xff1a; 1、拉普拉斯&#xff08;Laplace&#xff09;方法&#xff0c;將重力場展開為球諧級數。 2、斯托克斯&#xff08;Stokes&#xff09;方法&#xff0c;根據地球的總質量和旋轉角速度計算。 本篇主要說第一種方法&#xff0c;該方法將地…

MySQL的視圖

一、MySQL視圖的介紹和作用 MySQL視圖&#xff0c;加油兄弟們&#xff0c;孰能生巧&#xff0c;完整代碼在最后&#xff01;&#xff01;&#xff01; 視圖是一個虛擬的表&#xff0c;并不是真是存在的&#xff0c;視圖其實并沒有真實的數據&#xff0c;他只是根據一個sql語句…

Scala與Go的異同教程

當瑞士軍刀遇到電鋸&#xff1a;Scala vs Go的相愛相殺之旅 各位準備禿頭的程序猿們&#xff08;放心&#xff0c;用Go和Scala不會加重你的發際線問題&#xff09;&#xff0c;今天我們來聊聊編程界的"冰與火之歌"——Scala和Go的異同。準備好瓜子飲料&#xff0c;我…

SaaS場快訂平臺項目說明【持續更新】

一、項目介紹 SaaS場快訂平臺是一個高效、便捷的體育場館在線預訂平臺。本項目采用SaaS方式開發&#xff0c;用戶不需要安裝軟件&#xff0c;直接通過互聯網訪問在線程序即可使用。本項目主要構建了一個體育館預訂系統&#xff0c;項目的功能主要包括&#xff1a;用戶注冊與登…

linux中常用的命令(三)

目錄 1- ls(查看當前目錄下的內容) 2- pwd (查看當前所在的文件夾) 3- cd [目錄名]&#xff08;切換文件夾&#xff09; 4- touch [文件名] &#xff08;如果文件不存在&#xff0c;新建文件&#xff09; 5- mkdir[目錄名] &#xff08;創建目錄&#xff09; 6-rm[文件名]&…

使用Simulink開發Autosar Nvm存儲邏輯

文章目錄 前言Autosar Nvm接口設計模型及接口生成代碼及arxmlRTE接口mappingRTE代碼分析總結 前言 之前介紹過Simulink開發Dem故障觸發邏輯&#xff0c;本文接著介紹另外一個常用的功能-Nvm存儲的實現。 Autosar Nvm接口 Autosar Nvm中一般在上電初始化的時調用Nvm_ReadAll獲…

Java—— 泛型詳解

泛型概述 泛型是JDK5中引入的特性&#xff0c;可以在編譯階段約束操作的數據類型&#xff0c;并進行檢查。 泛型的格式&#xff1a;<數據類型> 注意&#xff1a;泛型只能支持引用數據類型。 泛型的好處 沒有泛型的時候&#xff0c;可以往集合中添加任意類型的數據&#x…

通俗的橋接模式

橋接模式&#xff08;Bridge Pattern&#xff09; 就像一座橋&#xff0c;把兩個原本獨立變化的東西連接起來&#xff0c;讓它們可以各自自由變化&#xff0c;互不干擾。簡單來說&#xff0c;就是 “把抽象和實現分開&#xff0c;用組合代替繼承”。 一句話理解橋接模式 假設你…

【現代深度學習技術】注意力機制04:Bahdanau注意力

【作者主頁】Francek Chen 【專欄介紹】 ? ? ?PyTorch深度學習 ? ? ? 深度學習 (DL, Deep Learning) 特指基于深層神經網絡模型和方法的機器學習。它是在統計機器學習、人工神經網絡等算法模型基礎上&#xff0c;結合當代大數據和大算力的發展而發展出來的。深度學習最重…

爬蟲學習————開始

&#x1f33f;自動化的思想 任何領域的發展原因————“不斷追求生產方式的改革&#xff0c;即使得付出與耗費精力越來愈少&#xff0c;而收獲最大化”。由此&#xff0c;創造出方法和設備來提升效率。 如新聞的5W原則直接讓思考過程規范化、流程化。或者前端框架/后端輪子的…