项目——博客系统
文章目录
- 项目优点
- 项目创建
- 创建相应的目录,文件,表,导入前端资源
- 实现common工具类
- 实现拦截器验证用户登录
- 实现统一数据返回格式
- 实现加盐加密类
- 实现encrypt方法
- 实现decrypt方法
- 实现SessionUtil类
- 实现注册页面
- 实现前端代码
- 实现后端代码
- 实现登录页面
- 实现前端代码
- 实现后端代码
- 实现个人主页
- 实现退出登录功能
- 实现前端代码
- 实现后端代码
- 初始化个人信息(包括个人文章列表的个人信息)和完成删除功能
- 实现前端代码
- 实现后端代码
- 实现详情页(blog_content.html)
- 实现前端代码
- 实现后端代码
- 实现博客修改功能(blog_update.html)
- 实现前端代码
- 实现后端代码
- 实现博客编辑页
- 实现前端代码
- 实现后端代码
- 实现我的草稿箱(draft_list.html)
- 实现前端代码
- 实现后端代码
- 实现博客主页(blog_list.html)
- 实现前端代码
- 实现后端代码
- 将session持久化到redis
- 其他扩展功能
前言
这个博客系统前端分为8个页面,分别是注册页,登录页,编辑页,修改页,个人主页,博客正文页,草稿列表页,博客列表页
项目优点
- 框架:使用ssm(SpringBoot + SpringMVC + MyBatis)
- 密码:用户登录用的密码是使用加盐算法处理然后存储到数据库
- 用户登录状态持久化:将session持久化到redis
- 功能升级:在博客列表页实现一个分页功能
- 使用拦截器进行用户的登录验证,统一数据返回格式,统一异常处理
项目创建
创建一个SpringBoot项目,添加需要的依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></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><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
创建相应的目录,文件,表,导入前端资源
首先在数据库建表,这里直接提供sql语句
-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;-- 使用数据数据
use mycnblog;-- 创建表[用户表]
drop table if exists userinfo;
create table userinfo(id int primary key auto_increment,username varchar(100) not null,password varchar(32) not null,photo varchar(500) default '',createtime datetime default now(),updatetime datetime default now(),`state` int default 1
) default charset 'utf8mb4';-- 创建文章表
drop table if exists articleinfo;
create table articleinfo(id int primary key auto_increment,title varchar(100) not null,content text not null,createtime datetime default now(),updatetime datetime default now(),uid int not null,rcount int not null default 1,`state` int default 1
)default charset 'utf8mb4';-- 添加一个用户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);-- 文章添加测试数据
insert into articleinfo(title,content,uid,state)values('喜讯','今天是正月初六',9,2);
- 实体层(model):创建实体类UserInfo,ArticleInfo
- 控制层(controller):创建控制器UserController和ArticleInfo
- 服务层(servlce):创建服务类:UserService和ArticleService
- 持久层(mapper):创建接口UserMapper和ArticleMapper,并创建对应的xml文件,在yml文件里配置好
- 工具层(common):统一返回类(ResponseAdvice,AjaxResult等)
注意:创建完相应的类就得将需要加的注解加上去,这里就不一一展示了
创建完后的目录结构

yml配置文件的内容
# 配置数据库的连接字符串
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8username: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver
# 设置 Mybatis 的 xml 保存路径
mybatis:mapper-locations: classpath:mybatis/**Mapper.xmlconfiguration: # 配置打印 MyBatis 执行的 SQLlog-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置打印 MyBatis 执行的 SQL
logging:level:com:example:demo: debug
将前端的东西导入static


实现common工具类

由于工具类后面我们基本都会用到,所以这里先实现一下
实现拦截器验证用户登录
- 步骤1创建一个Constant类,定义session的key
- 步骤2:创建一个普通类实现HandlerInterceptor接口,重写preHandle
- 步骤3:创建一个普通类实现 WebMvcConfigurer接口,重写addInterceptors
public class Constant {//登录信息存储到session中的key值public static final String SESSION_USERINFO_KEY = "session_userinfo_key";
}
@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断登录业务HttpSession session = request.getSession(false);//会根据请求发送来的sessionId去服务器找对应的会话if(session.getAttribute(Constant.SESSION_USERINFO_KEY)!=null) {//根据key拿到valuereturn true;}response.setStatus(401);return false;}
}
@Configuration
public class AppConfig implements WebMvcConfigurer {//不拦截的urlList<String> excludes = new ArrayList<>() {{add("/**/*.html");add("/js/**");add("/editor.md/**");add("/css/**");add("/img/**"); // 放行 static/img 下的所有文件}};@AutowiredLoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//配置拦截器InterceptorRegistration registration = registry.addInterceptor(loginInterceptor);registration.addPathPatterns("/**");registration.excludePathPatterns(excludes);}
}
实现统一数据返回格式
步骤1:创建一个普通的类,实现业务成功返回的方法和业务失败返回的方法
步骤2:创建一个普通的类实现ResponseAdvice接口,重写supports方法和beforeBodyWrite方法
代码
public class AjaxResult {/*** 业务执行成功返回的方法* @param data* @return*/public static HashMap<String,Object> success(Object data) {HashMap<String,Object> result = new HashMap<>();result.put("code",200);result.put("msg","");result.put("data",data);return result;}/*** 业务执行成功时返回的代码* @param data* @param msg* @return*/public static HashMap<String,Object> success(Object data,String msg) {HashMap<String,Object> result = new HashMap<>();result.put("code",200);result.put("msg",msg);result.put("data",data);return result;}/*** 业务执行失败返回的方法* @param code* @param data* @param msg* @return*/public static HashMap<String,Object> fail(int code,Object data,String msg) {HashMap<String,Object> result = new HashMap<>();result.put("code",code);result.put("msg",msg);result.put("data",data);return result;}/*** 业务执行失败返回的方法* @param code* @param msg* @return*/public static HashMap<String,Object> fail(int code,String msg) {HashMap<String,Object> result = new HashMap<>();result.put("code",code);result.put("msg",msg);result.put("data","");return result;}
}
public class ResponseAdvice implements ResponseBodyAdvice {@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 HashMap) {return body;}//如果body是字符串类型,需要特殊处理if(body instanceof String) {ObjectMapper objectMapper = new ObjectMapper();return objectMapper.writeValueAsString(AjaxResult.success(body));}return AjaxResult.success(body);}
}
实现加盐加密类
创建一个普通类,实现两个方法,encrypt方法和decrypt方法。
- encrypt方法根据用户输入的密码,进行加盐加密,返回最终加密的密码
- decrypt方法根据用户输入的密码和数据库存的加密的密码进行验证
public class SecurityUtil {/*** 对password进行加盐加密* @param password* @return*/public static String encrypt(String password) {}/*** 验证password和数据库拿出来的finalPassword进行验证* @param password* @param finalPassword* @return*/public static String decrypt(String password,String finalPassword) {}
}
实现encrypt方法
加盐思路:用UUID类生成一个32长度的字符串作为盐值,然后将盐值+password进行md5加密生成一个32长度的字符串,然后盐值+这个有md5加密的字符串,就是最终的结果
/*** 对password进行加盐加密* @param password* @return*/public static String encrypt(String password) {//每次生成内容不同,但是长度固定为32的字符串String salt = UUID.randomUUID().toString().replace("-","");//盐值+password进行md5加密String finalPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());//返回盐值+密码,总共64位,存到数据库中return salt+finalPassword;}
实现decrypt方法
这个方法用来解密验证密码
思路:这个方法有两个参数password:待验证的密码,finalPassword:最终正确的密码(在数据库查询的密码),从finalPassword中提取出盐值,然后盐值+password进行md5加密生成一个字符串,然后盐值+字符串和finalPassword判断是否相等
/*** 验证password和数据库拿出来的finalPassword进行验证* password:待验证的密码* finalPassword:最终正确的密码(在数据库查询的密码)* @param password* @param finalPassword* @return*/public static boolean decrypt(String password,String finalPassword) {//非空效验if(!StringUtils.hasLength(password) || !StringUtils.hasLength(finalPassword)) {return false;}if(finalPassword.length()!=64) {return false;}//提取出盐值String salt = finalPassword.substring(0,32);//使用盐值+密码生成一个32位的密码String securityPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());//使用上一个32位的密码拼接上盐值进行密码验证return (salt+securityPassword).equals(finalPassword);}
实现SessionUtil类
这个工具类用来查询当前用户登录的session信息
public class SessionUtil {public static UserInfo getLoginUser(HttpServletRequest request) {HttpSession session = request.getSession(false);if(session!=null && session.getAttribute(Constant.SESSION_USERINFO_KEY)!=null) {UserInfo userInfo = (UserInfo) session.getAttribute(Constant.SESSION_USERINFO_KEY);return userInfo;}return null;}
}
实现注册页面
这里开始就涉及到前后端交互了,要约定好前后端交互的接口
实现前端代码
那先来编写前端代码(打开reg.html进行编写代码)
记得要引入jquery

mysub方法
function mysub() {//1.非空效验var username = jQuery("#username");var password = jQuery("#password");var password2 = jQuery("#password2");if(username.val()=="") {alert("用户名为空");username.focus();return false;}if(password.val()=="") {alert("密码为空");password.focus();return false;}if(password2.val()=="") {alert("确认密码为空");password2.focus();return false;}if(password.val()!=password2.val()) {alert("两次输入的密码不一致");return false;}//2.发送ajax请求jQuery.ajax({url:"/user/reg",type:"POST",data:{username:username.val(),password:password.val()},success:function(result) {if(result.code==200 && result.data==1) {alert("注册成功");if(confirm("是否去登录?")) {location.href="login.html";}} else if(result.code==-2 && result.msg!=null) {alert("注册失败,用户名已存在");} else {alert("注册失败请重试");}}});}
实现后端代码
由前端代码可知,url为/user/reg,我们需要在AppConfig里放行这个url,因为默认是所有url都要拦截,但是注册不能拦截,因为还没注册怎么能登录呢

后端代码基本思路就是,controller调用service,service调用mapper
所以要在UserController中注入UserService,在UserService中注入UserMapper


在UserController中实现reg方法
@RequestMapping("/reg")public Object reg(String username,String password) {//1.非空效验if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {return AjaxResult.fail(-1,"非法参数");}//根据用户名查询用户UserInfo userInfo = userService.getUserByName(username);if(userInfo!=null && userInfo.getId()>0) {//用户名已经存在return AjaxResult.fail(-2,"用户名已存在");}//2.进行添加操作int result = userService.add(username, SecurityUtil.encrypt(password));if(result==1) {return AjaxResult.success(1,"添加成功");} else {return AjaxResult.fail(-1,"添加失败");}}
可以看到,我们还需要在UserService中实现getUserByName和add方法
public UserInfo getUserByName(String username) {return userMapper.getUserByName(username);}public int add(String username,String password) {return userMapper.add(username,password);}
实现完之后,还需要在UserMapper接口中定义getUserByName方法和add方法,然后在对应的xml文件中实现sql语句
UserMapper
public UserInfo getUserByName(@Param("username") String username);public int add(@Param("username") String username,@Param("password") String password);
对应的UserMapper.xml文件

<select id="getUserByName" resultType="com.example.demo.model.UserInfo">select * from userinfo where username=#{username}</select><insert id="add">insert into userinfo(username,password)values(#{username},#{password})</insert>
到这里注册页面就实现完成了
实现登录页面
实现前端代码
打开login.html进行编写代码
还是一样,引入jquery,然后在submit设置onclick监听,然后实现mysub()方法

function mysub() {//1.非空效验var username = jQuery("#username");var password = jQuery("#password");if(username.val()=="") {alert("用户名为空");username.focus();return false;}if(password.val()=="") {alert("密码为空");password.focus();return false;}//2.发送ajax请求jQuery.ajax({url:"/user/login",type:"POST",data:{"username":username.val(),"password":password.val()},success:function(result) {if(result.code==200 && result.data==1) {location.href="myblog_list.html";} else {alert("用户名或密码错误");username.focus();}}});}
实现后端代码
先放行/user/login接口
然后在UserController中编写login方法
@RequestMapping("/login")public int login(HttpServletRequest request,String username,String password) {//1.非空效验if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {return 0;}//2.进行数据库查询操作UserInfo userInfo = userService.getUserByName(username);if(userInfo!=null && userInfo.getId()>0) {//用户名正确,验证密码if(SecurityUtil.decrypt(password,userInfo.getPassword())) {//将userInfo存到session中HttpSession session = request.getSession(true);session.setAttribute(Constant.SESSION_USERINFO_KEY,userInfo);return 1;}}return 0;}
实现个人主页
实现退出登录功能
当然由于退出登录功能在很多页面中都应该存在,所以后面可能就不详细说明
实现前端代码
退出登录功能后面很多页面都会用,所以我们新建一个Tool.js文件,将退出登录的前端方法写在里面

然后在Tool.js中编写代码
//退出登录
function onExit() {if(confirm("是否确认退出?")) {//发送ajax请求退出jQuery.ajax({url:"/user/logout",type:"POST",data:{},success: function(result) {location.href="login.html";},error:function(err) {if(err!=null && err.status==401) {alert("用户未登录,即将跳转登录页面");location.href="login.html";}}});}
}
编写myblog_list.html文件
并引入Tool.js和jquery

然后只需要修改一句代码即可

当你点击退出登录,它会自动调用Tool.js里的onExit()方法
实现后端代码
当用户点击退出登录,发送ajax请求到后端时,后端就将用户对应的session给删除即可。
在UserController中编写代码
@RequestMapping("/logout")public boolean logout(HttpServletRequest request) {HttpSession session = request.getSession(false);if(session!=null && session.getAttribute(Constant.SESSION_USERINFO_KEY)!=null) {session.removeAttribute(Constant.SESSION_USERINFO_KEY);}return true;}
初始化个人信息(包括个人文章列表的个人信息)和完成删除功能
当跳转到myblog_list.html时,就前端就调用initList()和myinfo()方法,这两个方法发送ajax请求去查询数据库,然后将个人信息和个人发表的文章返回给前端,前端再根据数据进行渲染
实现前端代码
<script>var descLen = 60;//简介最大长度//这个方法用来从正文中提取字符串function mySubStr(content) {if(content.length>desLen) {return content.substr(0,descLen);}return content;}//初始化个人列表信息function initList() {jQuery.ajax({url:"/art/mylist",type:"POST",data:{},//不用传uid,因为session中有userinfo,不能轻信前端传来的参数success:function(result) {if(result.code==200 && result.data!=null && result.data.length>0) {var html = "";for(var i=0;i<result.data.length;i++) {var item = result.data[i];//如果state==2,说明是草稿箱里的文章,不显示出来if(item.state==2) {continue;}html+='<div class="blog">';html+='<div class="title">'+item.title+'</div>';html+='<div class="date">'+item.createtime+'</div>'+'<div class="desc">'+mySubstr(item.content)+' </div>';html+='<div style="text-align: center;margin-top: 50px;">';html+='<a href="blog_content.html?id='+item.id+'">查看详情</a> ';html+='<a href="blog_update.html?id='+item.id+'">修改</a> <a href="javascript:myDel('+item.id+')">删除</a></div>';html+='</div>';}jQuery("#artlistDiv").html(html);} else {//此人没有发表文章jQuery("#artlistDiv").html("<h1>暂无数据</h1>");}},error:function(err) {if(err!=null && err.statue==401) {alert("用户未登录,即将跳转登录页面");location.href="login.html";}}});}initList();//当浏览器渲染引擎执行到此行时就会调用此方法//获取个人信息function myinfo() {jQuery.ajax({url:"user/myinfo",type:"POST",data:{},success:function(result) {if(result.code==200 && result.data!=null) {jQuery("#username").text(result.data.username);}},error:function(err) {if(err!=null && err.status==401) {alert("用户未登录,即将跳转登录页面");location.href="login.html";}}});}myinfo();//删除功能function myDel(aid) {jQuery.ajax({url:"art/del",type:"POST",data:{"aid":aid},success:function(result) {if(result.code==200 && result.data==1) {alert("删除成功");location.href="myblog_list.html";}},error:function(err) {if(err!=null && err.status==401) {alert("用户未登录");location.href="login.html"} else {alert("删除失败");location.href="myblog_list.html"}}});}</script>
代码很长,但其实主要也就三个主要的方法,这3个方法都有ajax请求,所以需要在后端进行处理,然后返回结果
实现后端代码
先处理initList的ajax请求
对于initList发送来的ajax请求,我们要根据sesion里存的userInfo的id去查文章表拿到该用户的所有文章信息,然后进行返回
同样,先进行注入

@RequestMapping("/mylist")public List<ArticleInfo> myList(HttpServletRequest request) {UserInfo userInfo = SessionUtil.getLoginUser(request);if(userInfo!=null) {return articleService.getMyList(userInfo.getId());}return null;}
接下来一样要在ArticleService中实现getMyList方法

在ArticleService中也要先注入
public List<ArticleInfo> getMyList(Integer uid) {return articleMapper.getMyList(uid);}
接下来在ArticleMapper中定义getMyList方法,然后在对应的xml文件中编写sql语句
public List<ArticleInfo> getMyList(@Param("uid") Integer uid);
<select id="getMyList" resultType="com.example.demo.model.ArticleInfo">select * from articleinfo where uid=#{uid}</select>
接下来处理myinfo的ajax请求
@RequestMapping("/myinfo")public UserInfo myInfo(HttpServletRequest request) {HttpSession session = request.getSession(false);if(session!=null && session.getAttribute(Constant.SESSION_USERINFO_KEY)!=null) {UserInfo userInfo = SessionUtil.getLoginUser(request);return userInfo;}return null;}
接下来处理myDel发送来的ajax请求
@RequestMapping("/del")public Integer artDel(HttpServletRequest request,Integer aid) {//非空效验if(aid!=null && aid>0) {//根据文章id查询文章详情ArticleInfo articleInfo = articleService.getDetail(aid);//进行归属人验证UserInfo userInfo = SessionUtil.getLoginUser(request);if(articleInfo!=null && userInfo!=null && userInfo.getId()!=articleInfo.getUid()) {//归属人正确进行删除int result = articleService.artDel(aid);return result;}}return null;}
接下来得在ArticleService中实现getDetail方法和artDel方法
public ArticleInfo getDetail(Integer aid) {return articleMapper.getDetail(aid);}public Integer artDel(Integer aid) {return articleMapper.artDel(aid);}
接下来在ArticleMapper中定义getDetail方法和artDel方法,然后在对应的xml文件中编写sql语句
public ArticleInfo getDetail(@Param("aid") Integer aid);public Integer artDel(@Param("aid") Integer aid);
<select id="getDetail" resultType="com.example.demo.model.ArticleInfo">select * from articleinfo where id=#{aid}</select><delete id="artDel">delete from articleinfo where id=#{aid}</delete>
实现详情页(blog_content.html)

myblog_list.html的页面效果大概是这样的,其中删除功能已经实现了,那么现在来实现查看详情功能,当点击查看详情时,就会跳转blog_content.html页面,并将文章id传过去(如下图)

实现前端代码
那接下来打开blog_content.html来进行编写前端代码
这个页面同样也有退出登录功能,跟上面写法一样

在编写之前,我们需要在Tool.js中写一个方法getURLParam,这个方法用来从url上获取参数
这里直接给代码
// 获取当前 url 中某个参数的方法
function getURLParam(key){var params = location.search;if(params.indexOf("?")>=0){params = params.substring(1);var paramArr = params.split('&');for(var i=0;i<paramArr.length;i++){var namevalues = paramArr[i].split("=");if(namevalues[0]==key){return namevalues[1];}}}else{return "";}
}
然后开始编写blog_content.html
//获取文章详细信息function getArtDetail() {//从url中获取文章id,也就是在myblog_list.html跳转到这里时url中的参数var aid = getURLParam("id");if(aid!=null && aid>0) {//发送ajax请求,查询数据库拿到文章详情jQuery.ajax({url:"art/detail",type:"POST",data:{"aid":aid},success:function(result) {if(result.code==200 && result.data!=null) {var art = result.data;jQuery("#title").text(art.title);jQuery("#date").text(art.createtime);jQuery("#rcount").text(art.rcount);editormd = editormd.markdownToHTML("editorDiv",{markdown : art.content});//根据uid获取个人信息myInfo(art.uid);}}});}}getArtDetail();//根据uid获取个人信息function myInfo(uid) {jQuery.ajax({url:"user/myinfobyid",type:"POST",data:{"uid":uid},success:function(result) {if(result.code==200 && result.data!=null) {jQuery("#username").text(result.data.username);}},error:function(err) {if(err!=null && err.status==401) {alert("用户为登录,即将跳转登录页面");location.href="login.html"}}});}
实现后端代码
上面前端代码也是两个ajax请求
先来处理getArtDetail方法发送来的ajax请求
要在AppConfig中放行detail接口

@RequestMapping("/detail")public Object getDetail(Integer aid) {if(aid!=null && aid>0) {ArticleInfo articleInfo = articleService.getDetail(aid);//访问量加1synchronized (locker) {int result = articleService.rountAdd(aid,articleInfo.getContent()+1);if(result!=1) {return AjaxResult.fail(-1,"访问量添加失败");}}//返回文章详情return AjaxResult.success(articleInfo);}return AjaxResult.fail(-1,"查询失败");}
接下来要在ArtclieService中实现rcountAdd方法
public int rcountAdd(Integer aid,Integer rcount) {return articleMapper.rcountAdd(aid,rcount);}
接下来在ArticleMapper中定义rcountAdd方法并在对应的xml文件中编写sql语句
public int rcountAdd(@Param("aid") Integer aid,@Param("rcount") Integer rcount);
<update id="rcountAdd">update articleinfo set rcount=#{rcount} where id=#{aid}</update>
然后处理myinfo发送的ajax请求,要在AppConfig放行/user/myinfobyuid

@RequestMapping("/myinfobyuid")public UserInfo getMyInfoByUid(Integer uid) {if(uid!=null && uid>0) {return userService.getMyInfoByUid(uid);}return null;}
public UserInfo getMyInfoByUid(Integer uid) {return userMapper.getMyInfoByUid(uid);}
public UserInfo getMyInfoByUid(@Param("uid") Integer uid);
<select id="getMyInfoByUid" resultType="com.example.demo.model.UserInfo">select * from userinfo where id=#{uid}</select>
实现博客修改功能(blog_update.html)

当点击修改时,就会将跳转到blog_update.html并将id传过去
实现前端代码
同样这个页面有退出登录功能,实现方法跟上面一样

function mysub(){// alert(editor.getValue()); // 获取值// editor.setValue("#123") // 设置值\var title = jQuery("#title");var content = editor.getValue();//非空效验if(title=="") {title.focus();alert("请先输入标题");return false;}if(content=="") {alert("请输入正文");return false;}jQuery.ajax({url:"/art/update",type:"POST",data:{"aid":aid,"title":title.val(),"content":content},success:function(result) {if(result.code==200 && result.data>0) {alert("修改成功");location.href="myblog_list.html";} else {alert("修改失败,请重试");}},error:function(err) {if(err!=null && err.status==401) {alert("用户未登录");location.href="login.html";}}});}//查询文章详情并展现function showArt() {//从url中获取文章idaid = getURLParam("id");if(aid!=null && aid>0) {//访问后端详情jQuery.ajax({url:"/art/detailbyid",type:"POST",data:{"aid":aid},success:function(result) {if(result.code==200 && result.data!=null) {var art = result.data;jQuery("#title").val(art.title);initEdit(art.content);} else {alert("您没有权限修改");location.href="myblog_list.html";}},error:function(err) {if(err!=null && err.status==401) {alert("用户还未登录,即将跳转登录页面");location.href="login.html";}}});}}showArt();
实现后端代码
上面前端代码中涉及到两个ajax请求
先来处理showArt方法发送的ajax请求
@RequestMapping("/detailbyid")public Object getDetailById(HttpServletRequest request,Integer aid) {if(aid!=null && aid>0) {//根据文章id查询文章详情ArticleInfo articleInfo = articleService.getDetail(aid);//文章归属人验证UserInfo userInfo = SessionUtil.getLoginUser(request);if(articleInfo!=null && userInfo!=null && articleInfo.getUid()==userInfo.getId()) {//文章归属人正确return AjaxResult.success(articleInfo);}}return AjaxResult.fail(-1,"查询失败");}
接下来处理mysub发送的ajax请求
@RequestMapping("/update")public int update(HttpServletRequest request,Integer aid,String title,String content) {//非空效验if(!StringUtils.hasLength(title) || !StringUtils.hasLength(content) || aid == 0 || aid<=0) {return 0;}UserInfo userInfo = SessionUtil.getLoginUser(request);if(userInfo!=null && userInfo.getId()>0) {return articleService.update(aid,userInfo.getId(),title,content);}return 0;}
然后在ArticleService中实现update
public int update(Integer aid,Integer uid,String title,String content) {return articleMapper.update(aid,uid,title,content);}
然后在ArticleMapper中定义update并在对应的xml文件中编写sql语句
public int update(@Param("aid") Integer aid,@Param("uid") Integer uid,@Param("title") String title,@Param("content") String content);
<update id="update">update articleinfo set title=#{title},content=#{content}where id=#{aid} and uid=#{uid}</update>
实现博客编辑页
实现前端代码
function mysub(){// alert(editor.getValue()); // 获取值// editor.setValue("#123") // 设置值var title = jQuery("#title");var content = editor.getValue();//非空效验if(title=="") {title.focus();alert("请先输入标题");return false;}if(content=="") {alert("请先输入正文");return false;}jQuery.ajax({url:"/art/release",type:"POST",data:{"title":title.val(),"content":content},success:function(result) {if(result.code==200 && result.data>0) {alert("发布成功");location.href="myblog_list.html";} else {alert("发布失败,请重试");}},error:function(err) {if(err!=null && err.status==401) {alert("用户未登录,即将跳转登录页面");location.href="login.html";}}});}//检查是否已经登录function checkIsLogged() {jQuery.ajax({url:"/user/islogged",type:"GET",success:function(result) {return true;},error:function(err) {alert("用户为登录,即将跳转登录页面");location.href="login.html";}});}checkIsLogged();//保存文章到草稿箱,将state设置为2function draft() {var title = jQuery("#title");var content = editor.getValue();//非空效验if(title=="") {title.focus();alert("请先输入标题");return false;}if(content=="") {alert("请先输入正文");return false;}jQuery.ajax({url:"/art/draft",type:"POST",data:{"title":title.val(),"content":content},success:function(result) {if(result.code==200 && result.data>0) {alert("保存成功");location.href="myblog_list.html";} else {alert("发布失败,请重试");}},error:function(err) {if(err!=null && err.status==401) {alert("用户未登录,即将跳转登录页面");location.href="login.html";}}});}
实现后端代码
上面的前端代码总共涉及到3个ajax请求
先来处理 checkIsLogged方法的ajax请求,这个主要用来验证时候登录
@RequestMapping("/islogged")public int isLogged(HttpServletRequest request, HttpServletResponse response) {UserInfo userInfo = SessionUtil.getLoginUser(request);if(userInfo!=null) {return 1;}response.setStatus(401);return 0;}
然后处理mysub()方法的ajax请求,这个方法用来发布文章,默认state1说明是发布的文章,state2是保存在草稿箱的文章
@RequestMapping("/release")public int releaseArt(HttpServletRequest request,String title,String content) {//非空效验if(!StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {return 0;}UserInfo userInfo = SessionUtil.getLoginUser(request);if(userInfo!=null && userInfo.getId()>0) {int result = articleService.releaseArt(title,content,userInfo.getId());if(result==1) {return 1;}}return 0;}
还要在ArtcileService中实现releaseArt方法,然后在ArticleMapper中定义releaseArt方法,并在对应的xml文件中编写sql语句
@RequestMapping("/release")public int releaseArt(HttpServletRequest request,String title,String content) {//非空效验if(!StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {return 0;}UserInfo userInfo = SessionUtil.getLoginUser(request);if(userInfo!=null && userInfo.getId()>0) {int result = articleService.releaseArt(title,content,userInfo.getId());if(result==1) {return 1;}}return 0;}
public int releaseArt(@Param("title")String title,@Param("content") String content,@Param("uid") Integer uid);
<insert id="releaseArt">insert into articleinfo (title,content,uid) values (#{title},#{content},#{uid})</insert>
最后处理保存文章也就是draft()的ajax请求,处理方法是将文章保存到数据库中,但是要将字段state设置为2,然后前端在渲染时,如果state==2,文章会显示在我的草稿箱中
@RequestMapping("/draft")public int draftArt(HttpServletRequest request,String title,String content) {//非空效验if(!StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {return 0;}int state = 2;//将state设置为2UserInfo userInfo = SessionUtil.getLoginUser(request);if(userInfo!=null && userInfo.getId()>0) {//将文章保存到数据库,并将state设置为2,表示还未发布int result = articleService.draftArt(title,content,userInfo.getId(),2);return result;}return 0;}
还是老步骤在,ArticleService中实现draftArt,然后在ArticleMapper定义draftArt方法,并在对应的xml文件中编写sql语句
public int draftArt(String title,String content,Integer uid,Integer state) {return articleMapper.draftArt(title,content,uid,state);}
public int draftArt(@Param("title") String title,@Param("content") String content,@Param("uid") Integer uid,@Param("state") Integer state);
<insert id="draftArt">insert into articleinfo (title,content,uid,state) values (#{title},#{content},#{uid},#{state})</insert>
实现我的草稿箱(draft_list.html)

当点击我的草稿箱时,就会跳转到draft_list.html页面
实现前端代码
draft_list.html的代码基本和myblog_list.html的代码一样,最主要的区别主要是state2还是state2
var descLen = 60;//简介最大长度//字符串截取,将文章正文截取成简介function mySubstr(content) {if(content.length>descLen) {return content.substr(0,descLen);}return content;}//初始化个人列表信息function initList() {jQuery.ajax({url:"/art/mylist",type:"POST",data:{},//不用传uid,因为session中有userinfo,不能轻信前端的参数success:function(result) {if(result.code==200 && result.data!=null && result.data.length>0) {//todo:有文章var html="";var count = 0;for(var i=0;i<result.data.length;i++) {var item=result.data[i];//如果state==2说明是草稿箱里的文章,显示出来if(item.state==2) {html+='<div class="blog">';html+='<div class="title">'+item.title+'</div>';html+='<div class="date">'+item.createtime+'</div>'+'<div class="desc">'+mySubstr(item.content)+' </div>';html+='<div style="text-align: center;margin-top: 50px;">';html+='<a href="javascript:publish('+item.id+')">发布文章</a> ';html+='<a href="blog_update.html?id='+item.id+'">修改</a> <a href="javascript:myDel('+item.id+')">删除</a></div>';html+='</div>';count++;}}jQuery("#artlistDiv").html(html); if(count==0) {//此人草稿箱没有文章jQuery("#artlistDiv").html("<h1>暂无数据</h1>");} } },error:function(err) {if(err!=null&&err.status==401) {alert("用户未登录,即将跳转登录页面");location.href="login.html";}}});}initList();//当浏览器渲染引擎执行到此行时就会调用此方法//获取个人信息function myInfo() {jQuery.ajax({url:"/user/myinfo",type:"POST",data:{},success:function(result) {if(result.code==200 && result.data!=null) {jQuery("#username").text(result.data.username);}},error:function(err) {if(err!=null&&err.status==401) {alert("用户未登录,即将跳转登录页面");location.href="login.html";}}});}myInfo();//删除功能function myDel(aid) {jQuery.ajax({url:"/art/del",type:"POST",data:{"aid":aid},success:function(result) {if(result.code==200 && result.data==1) {alert("删除成功");location.href="draft_list.html";}},error:function(err) {if(err!=null&&err.status==401) {alert("该用户没有删除权限!");} else {alert("删除失败!");}location.href="draft_list.html";}});}//将草稿箱里的文章发布:将state设置成1function publish(aid) {jQuery.ajax({url:"/art/publish",type:"POST",data:{"aid":aid},success:function(result) {if(result.code==200 && result.data==1) {alert("发布成功");location.href="myblog_list.html";}},error:function(err) {if(err!=null&&err.status==401) {alert("该用户没有发布权限!");} else {alert("发布失败!");}location.href="draft_list.html";}});}
实现后端代码
上面涉及到的3个ajax请求,前两个ajax请求在之前的后端代码已经处理了,所以这里只需要再处理publish方法里的ajax请求即可
@RequestMapping("/publish")public int publishArt(Integer aid) {if(aid!=null) {//将state设置为1int result = articleService.publishArt(aid,1);if(result==1) {return 1;}}return 0;}
然后在ArticleService中实现draftArt方法,接着在ArticleMapper中定义draftArt方法并在对应的xml文件中编写sql语句
public int publishArt(Integer aid,Integer state) {return articleMapper.publishArt(aid,state);}
public int publishArt(@Param("aid") Integer aid,@Param("state") Integer state);
<update id="publishArt">update articleinfo set state=#{state} where id=#{aid}</update>
实现博客主页(blog_list.html)

博客主页存放所有的博客,有5个按钮分别是,查看全文,首页,上一页,下一页和末页
首页,上一页,下一页和末页就属于分页功能了,接下来讲一下分页的思路
分页要素:
- 页码(pageIndex):要查询第几页的数据
- 每页显示多少条数据(pageSize):每页展示最大长度数据
首先所有的数据都是从数据库中去查询的,查询的语句中有一个关键字limit,这样就可以限制发送多少条记录去前端,比如limit pageSize ,就查询pageSize条记录,然后发送去前端然后进行渲染页面
数据库还有一个关键字就是offset(偏移量),比如说limit 2 offer 2,就是跳过前两条记录,然后查询第3,第4条记录。
那么比如你想查询第pageIndex页的数据,那么它的偏移量就是pageSize*(pageIndex-1)
所以分页公式(偏移量):pageSize*(pageIndex-1)
分页语法:select * from articleinfo limit pageSize offset pageSize * (pageIndex-1)
实现前端代码

var descLen = 200;//简介最大长度//字符串截取,将文章正文截取成简介function mySubstr(content) {if(content.length>descLen) {return content.substr(0,descLen);}return content;}var pindex = 1;//当前的页码var psize = 2;//每页显示的条数信息var totalpage = 1;//总页数//初始化分页参数,尝试从url中获取pindex和psizefunction initPageParam() {var pi = getURLParam("pindex");if(pi!="") {pindex=pi;}var ps = getURLParam("psize");if(ps!="") {psize=ps;}}initPageParam();//查询分页数据function getList() {jQuery.ajax({url:"/art/list",type:"GET",data:{"pindex":pindex,"psize":psize},success:function(result) {if(result.code==200 && result.data!=null && result.data.length>0) {//循环拼接控制documentvar finalHtml = "";for(var i=0;i<result.data.length;i++) {var item = result.data[i];//如果state==2说明是草稿箱的文章,不显示出来if(item.state==2) {continue;}finalHtml+='<div class="blog">';finalHtml+='<div class="title">'+item.title+'</div>';finalHtml+='<div class="date">'+item.createtime+'</div>';finalHtml+='<div class="desc">'+mySubstr(item.content)+'</div>';finalHtml+='<a href="blog_content.html?id='+item.id+'" class="detail">查看全文</a>';finalHtml+='</div>';}jQuery("#listDiv").html(finalHtml);}}});}getList();//查询总共多少页function getTotalPage() {jQuery.ajax({url:"/art/totalpage",type:"GET",data:{"psize":psize},success:function(result) {if(result.code==200 && result.data!=null) {totalpage=result.data;}}});}getTotalPage();//首页function firstClick() {location.href="blog_list.html";}//上一页function beforeClick() {if(pindex<=1) {alert("前面已经没有内容了!");return false;}pindex-=1;location.href="blog_list.html?pindex="+pindex+"&psize="+psize;}//下一页function nextClick() {pindex = parseInt(pindex);if(pindex>=totalpage) {//已经是最后一页alert("后面已经没有内容了哦!");return false;}pindex+=1;location.href="blog_list.html?pindex="+pindex+"&psize="+psize;}//末页function lastClick() {pindex=totalpage;location.href="blog_list.html?pindex="+pindex+"&psize="+psize;}
实现后端代码
上面涉及到两个ajax请求
两个请求的url都需要放行

先来处理getTotalPage方法的ajax请求
@RequestMapping("/totalpage")public Integer getTotalCount(Integer psize) {if(psize!=null) {//参数有效int totalCount = articleService.getTotalCount();int totalPage = (int)Math.ceil(totalCount*1.0/psize);return totalPage;}return null;}
接下来在ArticleService中实现getTotalCount方法,然后在ArticleMapper中定义getTotalCount方法并在对应的xml文件中编写sql语句
public int getTotalCount() {return articleMapper.getTotalCount();}
public int getTotalCount();
<select id="getTotalCount" resultType="java.lang.Integer">select count(*) from articleinfo where state=1</select>
接下来处理getList的ajax请求
public List<ArticleInfo> getList(Integer psize,Integer offset) {return articleMapper.getList(psize,offset);}
然后在ArticleService中实现getList,还要在ArticleMapper中定义getList并在对应的xml文件中编写sql语句
public List<ArticleInfo> getList(@Param("psize") Integer psize,@Param("offset") Integer offset);
<select id="getList" resultType="com.example.demo.model.ArticleInfo">select * from articleinfo limit #{psize} offset #{offset}</select>
将session持久化到redis
首先你需要在你的linux环境上安装redis,这里就不介绍怎么安装了
然后在你的项目上的application.properties上配置连接redis即可,springboot已经内置了将session持久化到redis,所以你只需要在application.properties上配置即可
spring.redis.host=#你redis安装在哪台机器的ip地址
spring.redis.password=#redis的密码
spring.redis.port=6379#redis默认端口号
spring.redis.database=0#redis操作的数据库
spring.session.store-type=redis
server.servlet.session.timeout=1800#session存到redis的过期时间
spring.session.redis.flush-mode=on_save#会将session保存到redis本地
spring.session.redis.namespace=spring:session
其他扩展功能
- 定时发布功能
- 更换头像
- 冻结账号
等等,大家可以尝试自己实现,下面这个源代码链接,后续有新功能也会将源代码更新
源码链接:https://gitee.com/maze-white/project/tree/master/blog_system_ssm
相关文章:
项目——博客系统
文章目录项目优点项目创建创建相应的目录,文件,表,导入前端资源实现common工具类实现拦截器验证用户登录实现统一数据返回格式实现加盐加密类实现encrypt方法实现decrypt方法实现SessionUtil类实现注册页面实现前端代码实现后端代码实现登录页…...
PHP(14)会话技术
PHP(14)会话技术一、概念二、分类三、cookie技术1. cookie的基本使用2. cookie的生命周期3. cookie的作用范围4. cookie的跨子域5. cookie的数组数据四、session1. session原理2. session基本使用3. session配置4. 销毁session一、概念 HTTP协议是一种无…...
对JAVA 中“指针“理解
对于Java中的指针,以下典型案例会让你对指针的理解更加深刻。 首先对于: 系统自动分配对应空间储存数字 1,这个空间被变量名称b所指向即: b ——> 1 变量名称 空间 明…...
功率放大器在MEMS微结构模态测试研究中的应用
实验名称:功率放大器在MEMS微结构模态测试研究中的应用研究方向:元器件测试测试目的:随着MEMS器件在各个领域中广泛应用,对微结构进行模态测试获得其动态特性参数对微结构的设计、仿真、制造、以及质量控制和评价等方面具有十分重…...
【算法基础】字典树(Trie树)
一、Trie树原理介绍 1. 基本概念 Trie 树,也叫“字典树”。顾名思义,它是一个树形结构。它是一种专门处理字符串匹配的数据结构,用来解决在一组字符串集合中快速查找某个字符串的问题。【高效存储和查找字符串集合的数据结构】,存储形式如下: 2. 用数组来模拟Trie树的…...
MyBatis 插件 + 注解轻松实现数据脱敏
问题在项目中需要对用户敏感数据进行脱敏处理,例如身份号、手机号等信息进行加密再入库。解决思路就是:一种最简单直接的方式,在所有涉及数据敏感的查询到对插入时进行密码加解密方法二:有方法一到出现对所有重大问题的影响&#…...
MySQL优化篇-MySQL压力测试
备注:测试数据库版本为MySQL 8.0 MySQL压力测试概述 为什么压力测试很重要?因为压力测试是唯一方便有效的、可以学习系统在给定的工作负载下会发生什么的方法。压力测试可以观察系统在不同压力下的行为,评估系统的容量,掌握哪些是重要的变化…...
CF43A Football 题解
CF43A Football 题解题目链接字面描述题面翻译题面描述题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1样例 #2样例输入 #2样例输出 #2代码实现题目 链接 https://www.luogu.com.cn/problem/CF43A 字面描述 题面翻译 题面描述 两只足球队比赛,现给你进…...
Nginx常用命令及具体应用(Linux系统)
目录 一、常用命令 1、查看Nginx版本命令,在sbin目录下 2、检查配置文件的正确性 3、启动和停止Nginx 4、查看日志,在logs目录下输入指令: 5、重新加载配置文件 二、Nginx配置文件结构 三、Nginx具体应用 1、部署静态资源 2、反向代…...
从零实现Web服务器(三):日志优化,压力测试,实战接收HTTP请求,实战响应HTTP请求
文章目录一、日志系统的运行流程1.1 异步日志和同步日志的不同点1.2 缓冲区的实现二、基于Webbench的压力测试三、HTTP请求报文解析http报文处理流程epoll相关代码服务器接收http请求四、HTTP请求报文响应一、日志系统的运行流程 步骤: 单例模式(局部静态变量懒汉…...
MFC入门
1.什么是MFC?全称是Microsoft Foundation Class Library,我们称微软基础类库。它封装了windows应用程序的各种API以及相关机制的C类库MFC是一个大的类库MFC是一个应用程序框架MFC类库常用的头文件afx.h-----将各种MFC头文件包含在内afxwin.h-------包含了各种MFC窗…...
1、H5+CSS面试题
1, HTML5中新增了哪些内容?广义上的html5指的是最新一代前端开发技术的总称,包括html5,CSS3,新增的webAPI。Html中新增了header,footer,main,nav等语义化标签,新增了video,audio媒体标签,新增了canvas画布。…...
亚马逊云科技重磅发布《亚马逊云科技汽车行业解决方案》
当今,随着万物智联、云计算等领域的高速发展,创新智能网联汽车和车路协同技术正在成为车企加速发展的关键途径,推动着汽车产品从出行代步工具向着“超级智能移动终端”快速转变。挑战无处不在,如何抢先预判?随着近年来…...
Springboot扩展点之FactoryBean
前言FactoryBean是一个有意思,且非常重要的扩展点,之所以说是有意思,是因为它老是被拿来与另一个名字比较类似的BeanFactory来比较,特别是在面试当中,动不动就问你:你了解Beanfactory和FactoryBean的区别吗…...
新库上线 | CnOpenDataA股上市公司交易所监管措施数据
A股上市公司交易所监管措施数据 一、数据简介 证券市场监管是指证券管理机关运用法律的、经济的以及必要的行政手段,对证券的募集、发行、交易等行为以及证券投资中介机构的行为进行监督与管理。 我国《证券交易所管理办法》第十二条规定,证券交易所应当…...
同步辐射XAFS表征方法的应用场景分析
X射线吸收精细结构XAFS表征方法是一种用于研究物质结构和化学环境的分析技术。XAFS 使用 X 射线照射到物质表面,并观察由此产生的 X 光吸收谱。 XAFS 技术通常应用于研究高分子物质、生物分子、纳米结构和其他类型的物质。例如,XAFS 可以用来研究高分子…...
06 antdesign react Anchor 不同页面之间实现锚点
react Anchor 不同页面之间实现锚点一、定义二、使用步骤三、开发流程(一)、组件(二)、页面布局(三)、点击事件(四)、总结说明一、react单页面应用,当前页面的锚点二、react单页面应用,不同页面的锚点思路:锚点只能在当前页面使用,…...
mysql调优-内存缓冲池
因本地查询和服务器查询相比服务器慢了很多,同样的数据,同样的sql查询,考虑了是不是链接太多了,自行查询了下,我使用的c3p0的链接池,配置一个小时超时,正常情况下是20多个链接,而mys…...
【LeetCode】每日一题(5)
目录 题目:2341. 数组能形成多少数对 - 力扣(Leetcode) 题目的接口: 解题思路: 代码: 过啦!!! 写在最后: 题目:2341. 数组能形成多少数对 -…...
输入任意多个整数, 把这些数据保存到文件data.txt中.(按ctrl + z)
#pragma once #include <iostream> #include <fstream> using namespace std; /* 输入任意多个整数, 把这些数据保存到文件data.txt中. 如果在输入的过程中, 输入错误, 则提示用户重新输入. 指导用户输入结束(按ctrl z) [每行最多保存10个整数] */ int main() { …...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
基于TurtleBot3在Gazebo地图实现机器人远程控制
1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...
基于鸿蒙(HarmonyOS5)的打车小程序
1. 开发环境准备 安装DevEco Studio (鸿蒙官方IDE)配置HarmonyOS SDK申请开发者账号和必要的API密钥 2. 项目结构设计 ├── entry │ ├── src │ │ ├── main │ │ │ ├── ets │ │ │ │ ├── pages │ │ │ │ │ ├── H…...
Java数组Arrays操作全攻略
Arrays类的概述 Java中的Arrays类位于java.util包中,提供了一系列静态方法用于操作数组(如排序、搜索、填充、比较等)。这些方法适用于基本类型数组和对象数组。 常用成员方法及代码示例 排序(sort) 对数组进行升序…...
