项目——博客系统

news2024/11/18 11:36:20

文章目录

    • 项目优点
    • 项目创建
    • 创建相应的目录,文件,表,导入前端资源
    • 实现common工具类
      • 实现拦截器验证用户登录
      • 实现统一数据返回格式
      • 实现加盐加密类
        • 实现encrypt方法
        • 实现decrypt方法
      • 实现SessionUtil类
    • 实现注册页面
      • 实现前端代码
      • 实现后端代码
    • 实现登录页面
      • 实现前端代码
      • 实现后端代码
    • 实现个人主页
      • 实现退出登录功能
        • 实现前端代码
        • 实现后端代码
      • 初始化个人信息(包括个人文章列表的个人信息)和完成删除功能
        • 实现前端代码
        • 实现后端代码
    • 实现详情页(blog_content.html)
      • 实现前端代码
      • 实现后端代码
    • 实现博客修改功能(blog_update.html)
      • 实现前端代码
      • 实现后端代码
    • 实现博客编辑页
      • 实现前端代码
      • 实现后端代码
    • 实现我的草稿箱(draft_list.html)
      • 实现前端代码
      • 实现后端代码
    • 实现博客主页(blog_list.html)
      • 实现前端代码
      • 实现后端代码
    • 将session持久化到redis
    • 其他扩展功能

前言
在这里插入图片描述
这个博客系统前端分为8个页面,分别是注册页,登录页,编辑页,修改页,个人主页,博客正文页,草稿列表页,博客列表页

项目优点

  1. 框架:使用ssm(SpringBoot + SpringMVC + MyBatis)
  2. 密码:用户登录用的密码是使用加盐算法处理然后存储到数据库
  3. 用户登录状态持久化:将session持久化到redis
  4. 功能升级:在博客列表页实现一个分页功能
  5. 使用拦截器进行用户的登录验证,统一数据返回格式,统一异常处理

项目创建

创建一个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);

  1. 实体层(model):创建实体类UserInfo,ArticleInfo
  2. 控制层(controller):创建控制器UserController和ArticleInfo
  3. 服务层(servlce):创建服务类:UserService和ArticleService
  4. 持久层(mapper):创建接口UserMapper和ArticleMapper,并创建对应的xml文件,在yml文件里配置好
  5. 工具层(common):统一返回类(ResponseAdvice,AjaxResult等)

注意:创建完相应的类就得将需要加的注解加上去,这里就不一一展示了
创建完后的目录结构
在这里插入图片描述

yml配置文件的内容

# 配置数据库的连接字符串
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
# 设置 Mybatis 的 xml 保存路径
mybatis:
  mapper-locations: classpath:mybatis/**Mapper.xml
  configuration: # 配置打印 MyBatis 执行的 SQL
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置打印 MyBatis 执行的 SQL
logging:
  level:
    com:
      example:
        demo: debug

将前端的东西导入static
在这里插入图片描述

实现common工具类

在这里插入图片描述
由于工具类后面我们基本都会用到,所以这里先实现一下

实现拦截器验证用户登录

  1. 步骤1创建一个Constant类,定义session的key
  2. 步骤2:创建一个普通类实现HandlerInterceptor接口,重写preHandle
  3. 步骤3:创建一个普通类实现 WebMvcConfigurer接口,重写addInterceptors
public class Constant {
    //登录信息存储到session中的key值
    public static final String SESSION_USERINFO_KEY = "session_userinfo_key";
}

@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public 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拿到value
            return true;
        }
        response.setStatus(401);
        return false;
    }
}

@Configuration
public class AppConfig implements WebMvcConfigurer {
    //不拦截的url
    List<String> excludes = new ArrayList<>() {{
        add("/**/*.html");
        add("/js/**");
        add("/editor.md/**");
        add("/css/**");
        add("/img/**"); // 放行 static/img 下的所有文件
    }};

    @Autowired
    LoginInterceptor loginInterceptor;

    @Override
    public 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 {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @SneakyThrows
    @Override
    public 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>&nbsp;&nbsp;';
                            html+='<a href="blog_update.html?id='+item.id+'">修改</a>&nbsp;&nbsp;<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);
            //访问量加1
            synchronized (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中获取文章id
            aid = 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设置为2
        function 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设置为2
        UserInfo 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>&nbsp;&nbsp;';
                            html+='<a href="blog_update.html?id='+item.id+'">修改</a>&nbsp;&nbsp;<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设置成1
    function 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设置为1
            int 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个按钮分别是,查看全文,首页,上一页,下一页和末页

首页,上一页,下一页和末页就属于分页功能了,接下来讲一下分页的思路
分页要素:

  1. 页码(pageIndex):要查询第几页的数据
  2. 每页显示多少条数据(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和psize
    function 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) {
                    //循环拼接控制document
                    var 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/349834.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JS 动态爱心(HTML+CSS+JS)

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

webstorm开发electron,调试主进程方案

官网教程地址&#xff1a;https://www.electronjs.org/zh/docs/latest/tutorial/debugging-main-process 我只能说官网太看得起人了&#xff0c;整这么简易的教程…… 命令行开关 第一步还是要按要求在我们的package.json里加上端口监听&#xff1a;–inspect5858 我的命令…

恭喜山东翰林“智慧园区管理系统”获易知微可视化设计大赛二等奖

数字化经济发展是全球经济发展的重中之重&#xff0c;“数字孪生&#xff08;Digital Twin&#xff09;”这一词汇正在成为学术界和产业界的一个热点。数字孪生作为近年来的新兴技术&#xff0c;其与国民经济各产业融合不断深化&#xff0c;推动着各大产业数字化、网络化、智能…

关于服务连接器(Servlet)你了解多少?

Servlet 1 简介 Servlet是JavaWeb最为核心的内容&#xff0c;它是Java提供的一门动态web资源开发技术。 使用Servlet就可以实现&#xff0c;根据不同的登录用户在页面上动态显示不同内容。 Servlet是JavaEE规范之一&#xff0c;其实就是一个接口&#xff0c;将来我们需要定义…

血液透析过滤芯气密性检测装置中的高精度多段压力控制解决方案

摘要&#xff1a;针对目前血液过滤芯气密性检测过程中存在的自动化水平较低、多个检测压力之间需人工切换和压力控制精度较差的问题&#xff0c;为满足客户对高精度和自动化气密性检测的要求&#xff0c;本文提出了相应的解决方案。解决方案的主要特点是全过程的可编程压力控制…

Git的使用方法(保姆级)

一、安装git二、创建凭据 ①打开电脑的凭据管理器git:https://gitee.com是固定写法用户名、密码是你创建gitee的用户名、密码三、在gitee中创建一个仓库四、项目提交到仓库的方法①选择一个项目交由git管理按照步骤一中召唤小黑窗口输入 git init 就可以出现.git文件夹②右键选…

Golang基础 函数详解 匿名函数与闭包

文章目录01 匿名函数1.1 定义匿名函数1.2 匿名函数使用场景02 闭包2.1 闭包实现公有变量2.2 闭包实现缓存效果参考资料匿名函数是指不需要定义函数名的一种函数实现方式&#xff08;即没有名字的函数&#xff09;。匿名函数多用于实现回调函数和闭包。 01 匿名函数 Golang 支持…

财报解读:营收增长、亏损扩大,Shopify如何度过阵痛期?

后疫情时代&#xff0c;Shopify阵痛不断。 图源&#xff1a;Shopify 北京时间2023年2月16日&#xff0c;Shopify披露了2022年四季度财报&#xff0c;营收17.3亿美元&#xff0c;同比增长25.4%&#xff0c;高于分析师预期的16.5亿美元&#xff1b;净亏损为6.24亿美元&#xff0…

用上Visual Studio后,我的世界游戏的构建时间减少了一半

今天我们讲述一个使用 Visual Studio 提升工作效率的案例。 我的世界(Minecraft) 游戏开发商 Mojang Studios 近日联系了 Visual Studio C 团队&#xff0c;因为他们需要将 C 开发扩展到新平台&#xff08;Linux&#xff09;&#xff0c;同时还希望保留他们现有的技术基础&…

同源页面间的跨页面通信之BroadCast Channel

LocalStorage 通过LocalStorage存储内容&#xff0c;并且在改变某个tab页面后&#xff0c;另外一个tab页面监听变动这种方式应该大家都比较熟悉了。 监听变动的代码如下&#xff1a; 第二个tab页面监听如下 window.addEventListener(storage, function (e) {if (e.key ctc…

LeetCode-77. 组合

目录回溯法剪枝优化题目来源 77. 组合 回溯法 1.递归函数的返回值以及参数 在这里要定义两个全局变量&#xff0c;一个用来存放符合条件单一结果&#xff0c;一个用来存放符合条件结果的集合。 List<List<Integer>> result new ArrayList<>();LinkedList…

数据分析就要选择这款免费报表工具

对于一家企业来说&#xff0c;在日常运营的过程中本身就会产出很多的数据&#xff0c;那么这些数据本身就应该形成报表。可是如果只是选择手工的一种操作&#xff0c;确实需要浪费大量的人力物力。伴随着科技进入到快速发展的阶段&#xff0c;市面上更是出现了很多报表工具可以…

九龙证券|可转债一级市场回暖 14家上市公司可转债发行集中获批

可转债商场悄然升温。春节假期后&#xff0c;可转债新券上市体现普遍不错&#xff0c;多只个券首日涨幅打破30%&#xff0c;更有3个买卖日就实现翻倍的案例。一起&#xff0c;本周初可转债打新户数本年以来也首度站上1000万户大关。 因为新券盈余效应明显&#xff0c;可转债一级…

window10安装MySQL数据库

准备好软件MySql的下载参考&#xff1a;(1137条消息) mysql下载与安装过程_weixin_40396510的博客-CSDN博客_mysql数据库下载安装(1137条消息) 安装MySQL的常见问题_二木成林的博客-CSDN博客_sc不是内部或外部命令,也不是可运行的程序解压要C盘&#xff08;自定义&#xff0c;本…

Ubuntu——扩展磁盘空间,可视化软件简单很多

目录 1. 剩余空间查看 2. 虚拟机先分配 3. 安装与使用 gparted 1. 剩余空间查看 2. 虚拟机先分配 关闭虚拟机&#xff0c;打开虚拟机&#xff0c;但不启动&#xff0c;编辑虚拟机设置——点击硬盘- 拓展 设置扩展大小&#xff0c;确定。 但是此时我们的分区和文件 并没有扩容…

阅读笔记3——空洞卷积

空洞卷积 1. 背景 空洞卷积&#xff08;Dilated Convolution&#xff09;最初是为解决图像分割的问题而提出的。常见的图像分割算法通常使用池化层来增大感受野&#xff0c;同时也缩小了特征图尺寸&#xff0c;然后再利用上采样还原图像尺寸。特征图先缩小再放大的过程造成了精…

HummerRisk V0.9.1:操作审计增加百度云,增加主机检测规则及多处优化

HummerRisk V0.9.0发布&#xff1a;增加RBAC 资源拓扑图&#xff0c;首页新增检查的统计数据&#xff0c;云检测、漏洞、主机等模块增加规则&#xff0c;对象存储增加京东云&#xff0c;操作审计增加金山云&#xff0c;镜像仓库新增设置别名。 感谢社区中小伙伴们的反馈&#…

【surfaceflinger源码分析】surfaceflinger进程的消息驱动模型

概述 对于surfaceflinger大多数人都知道它的功能是做图形合成的&#xff0c;用英语表示就是指composite。其大致框图如下: 各个Android app将自己的图形画面通过surface为载体通过AIDL接口(Binder IPC)传递到surfaceflinger进程surfaceflinger进程中的composition engine与HW…

如何赋能智能运维,迈出数字化黑匣子第一步?

在当下大数据时代&#xff0c;诸多行业专家为企业智能运维绘出美好蓝图。在该蓝图中&#xff0c;互联网、云计算、大数据分析联合发力&#xff0c;企业在能“攻”能“守”中快速、可持续发展。何为“攻”&#xff1f;对支撑企业产品研发、生产、管理、营销等各业务链条的IT基础…

指针数组和数组指针、字符指针

文章目录指针字符指针实例一实例二指针数组实例一实例二实例三数组指针实例一实例二实例三-看书呗指针 1.指针是个变量&#xff0c;用来存放地址&#xff0c;地址将唯一标识一块内存空间 内存编号地址指针 2.指针的大小是固定的&#xff0c;32位平台是4个字节&#xff0c;64位…