项目——博客系统
文章目录
- 项目优点
- 项目创建
- 创建相应的目录,文件,表,导入前端资源
- 实现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() { …...
Mysql数据库的时间(3)一如何用函数插入时间
暂时用下面四个日期函数插入时间 如:insert into Stu(time) values (now()); Mysql的时间函数描述对应的Mysql的时间类型now()/sysdate()NOW()函数以YYYY-MM-DD HH:MM:SS返回当前的日期时间date/time/dateTime/timeStamp/yearcurDate()/current_date()返回当前的日期YYYY-M…...
关于eval函数(将JSON格式的字符串转换成JSON格式对象)
<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>关于eval函数</title> </head> <body> <!--JSON是一种行业内的数据交换格式标准。在JS当中以对象的形式存在…...
2023最强软件测试面试题,精选100 道,内附答案版,冲刺金3银4
精挑细选,整理了100道软件测试面试题,都是非常常见的面试题,篇幅较长,所以只放出了题目,答案在评论区! 测试技术面试题 1、什么是兼容性测试?兼容性测试侧重哪些方面? 2、我现在有…...
一文搞懂Docker容器里进程的 pid 是如何申请出来的?
如果大家有过在容器中执行 ps 命令的经验,都会知道在容器中的进程的 pid 一般是比较小的。例如下面我的这个例子。 # ps -ef PID USER TIME COMMAND1 root 0:00 ./demo-ie13 root 0:00 /bin/bash21 root 0:00 ps -ef 不知道大家是否和我一样…...
若依框架如何新增自定义主题风格
若依框架新增主题风格1.实现结果2.实现步骤2.1Settings目录下2.2 variables.scss2.3 sidebar.scss2.4 Logo.vue2.5 Siderbar目录下的index.vue1.实现结果 2.实现步骤 需要改动的文件目录: 2.1Settings目录下 <div class"setting-drawer-block-checbox-it…...
C语言格式化输入和输出; Format格式化
Format格式化 %1s或者%2s,%3s:取字符串的前1,2或者3位。%*c:屏蔽一个字符。%[A-Z]:取一个A到Z的值。 %[^a-z]:不取a到z的值。 %[^\n]:取非换行之前的值。printf("%5d", a):左边补 格式化:有正则在其中。 int main() {printf("%5d\n&quo…...
Revit教程:怎么关掉工具栏的实时提示?
一、Revit中如何关闭工具栏的实时帮助提示 如图1所示,Revit会对每一个命令有一个简单的图文说明,方便不熟悉软件的用户使用。对于已经熟悉软件的用户,会觉得鼠标在菜单上悬停时弹出的实时帮助页面很干扰使用,而且很占内存资源&…...
javascript 简介
JavaScript 是互联网上最流行的脚本语言,这门语言可用于 HTML 和 web,更可广泛用于服务器、PC、笔记本电脑、平板电脑和智能手机等设备。JavaScript 是脚本语言JavaScript 是一种轻量级的编程语言。JavaScript 是可插入 HTML 页面的编程代码。JavaScript…...
医学图象分割常用损失函数(附Pytorch和Keras代码)
对损失函数没有太大的了解,就是知道它很重要,搜集了一些常用的医学图象分割损失函数,学习一下! 医学图象分割常见损失函数前言1 Dice Loss2 BCE-Dice Loss3 Jaccard/Intersection over Union (IoU) Loss4 Focal Loss5 Tvesky Loss…...
【新2023】华为OD机试 - 病菌感染(Python)
华为 OD 清单查看地址:blog.csdn.net/hihell/category_12199275.html 病菌感染 题目 在一个地图中(地图有N*N个区域组成) 有部分区域被感染病菌 感染区域每天都会把周围上下左右的四个区域感染 请根据给定的地图计算多少天以后全部区域都会被感染 如果初始地图上所有区域都…...
网站开发专业公司有哪些/seo管家
[pixiv] https://www.pixiv.net/member_illust.php?modemedium&illust_id60481118 由于今天考了一道博弈的问题,我竟什么都不会!于是把之前大佬的讲稿翻出来从头学起 博弈论的基础嘛,就先不提什么SG函数了。简单的讲讲如何判断先手必胜…...
boostrop怎么做网站/深圳网络推广有几种方法
从今开波, 特此记录.转载于:https://blog.51cto.com/5589004/1598542...
自己做装修图网站/免费自己制作网站
2019独角兽企业重金招聘Python工程师标准>>> 之前对于Storm的Acker机制进行了一些数学上的描述。 在这里,对于Storm的Ack机制 在源码实现上进行一些有意的补充。 1: 在Ack框架的设计之中,Storm发射出去的消息都会对应于一个随机…...
自己有网站怎么优化/如何推广一个网站
当前,新一轮科技革命和产业革命蓬勃兴起,大数据、云计算、人工智能等现代信息技术与经济社会深度融合,给各行各业带来了深刻的变革。 在2018(第三届)中国大数据产业生态大会上,工业和信息化部党组成员、总…...
网站开发设计工具/网站关键词排名分析
对于该教程而言,缺少了删除已存在的电影记录的功能。因此,我在这里给出删除功能的代码供大家参考学习。 另外,需要注意的是要为VS2008打上SP1服务包,不然就不能使用ADO.NET Entity Data Model功能了。附按本教程制作的MovieDataba…...
做手机网站版面做多宽/百度网页版浏览器
首先新建一个文本,重新将后缀名改为.bat 右击编辑 输入start http://geekprank.com/hacker/--kiosk Ctrls保存。 start就是访问这个网址,当然我提供的这个网址还挺有意思的! 效果图如下: 结语:今天的代码你学废了吗&a…...