当前位置: 首页 > news >正文

项目:博客系统——基于SSM框架Mybatis-plus

基于SSM框架实现的一个博客系统,使用Mybatis-plus进行操作数据库

功能描述

首先进入界面,是一个登录的界面,在输入用户名和密码后登录成功,进入博客列表的页面,在博客列表有查看博客的功能,写博客的功能,注销账户的功能,点击查看博客的功能后,也有编辑博客,删除博客的功能。

功能展示

登录界面

列表页面 

 写作界面

查看博客界面 

编辑博客界面

接下来开始介绍我的项目

项目介绍

数据库的创建

 

一共有两张表

blog_info表 是博客列表内容的数据库

user_info表 是新增用户的信息

创建的项目

添加Spring MVC和Mybatis-plus的依赖

注:我们这个项目使用的是Mybatis-plus,所以创建好项目记得添加其依赖

        <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.5</version></dependency>

创建项目的结构目录

配置配置文件

由于我们后面需要将项目上传到服务器中,所以配置三个配置文件

包括:

application.yml(主配置文件):用来存放通常的配置

记得在pom文件中进行配置

  <profiles><profile><id>dev</id><properties><profile.name>dev</profile.name></properties></profile><profile><id>prog</id><properties><profile.name>prog</profile.name></properties></profile></profiles>

application-dev.yml(开发环境的配置文件):用来存放开放的时候需要的配置文件

application-prod.yml(生产环境的配置文件):用来存放生产的时候需要的配置文件

项目是采用三层架构

分为controller(控制层),service(服务层),mapper(持久层)

首先我们先写common(公共层)的代码

公共层一般是用于存储工具类,配置类

 首先定义业务状态的枚举

@AllArgsConstructor:自动生成带参构造器

@Getter
@AllArgsConstructor
public enum ResultCodeEnum {SUCCESS(200,"操作成功"),FAIL(-1,"操作失败");private int code;private String mes;}

统一返回结果的封装类 

定义了code(业务状态码),errMes(错误信息),data(返回的数据),其中包含三种方法

success方法,再输入成功返回的数据对象后,返回其数据成功的状态码和"操作成功这个信息"

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {private int code;//业务状态码private String errMes;private Object data;public static Result success(Object data){Result result=new Result();result.setData(data);result.setErrMes("操作成功");result.setCode(ResultCodeEnum.SUCCESS.getCode());return result;}public static Result fail(String errMes,Object data){Result result=new Result();result.setData(data);result.setErrMes(errMes);result.setCode(ResultCodeEnum.FAIL.getCode());return result;}public static Result fail(String errMes){Result result=new Result();result.setErrMes(errMes);result.setCode(ResultCodeEnum.FAIL.getCode());return result;}
}

最后就是统一返回结果

@RestControllerAdvice是全局的数据处理和异常处理,作用在@RestController注解下的方法返回的数据

@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Autowiredprivate ObjectMapper objectMapper;//对所有的controller方法执行,因为已经设置为true@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}//在controller返回数据后,在执行这个方法后返回响应体@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {//如果返回了String类型,进行封装if(body instanceof String){return objectMapper.writeValueAsString(Result.success(body));}//如果直接返回了Result类型,直接返回,避免重复的包装if(body instanceof Result){return body;}return Result.success(body);}
}

定义项目异常BlogException

自定义来处理关于博客业务会出现的异常

@Data
public class BlogException extends RuntimeException{private String errMsg;private int code;public BlogException(String errMsg) {this.errMsg=errMsg;}public BlogException(String errMsg,int code) {this.code=code;this.errMsg=errMsg;}
}

统一异常处理 

@ExceptionHandler 用于定义处理某种异常的方法,方法签名

@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {@ExceptionHandler(Exception.class)public Result exceptionHandler(Exception e){
//        log.error("Exception e:"+e);return Result.fail(e.getMessage());}@ExceptionHandler(BlogException.class)public Result exceptionHandler(BlogException e){
//        log.error("Exception e:"+e);return Result.fail(e.getErrMsg());}@ExceptionHandler(HandlerMethodValidationException.class)public Result exceptionHandler(HandlerMethodValidationException e){return Result.fail(e.getMessage());}
}

业务代码

实体类

记得对照数据库进行创建

博客列表

@Data
public class BlogInfo {@TableId(value = "id",type = IdType.AUTO) //设置id为主键,并且自增private Integer id;private String title;private String content;private Integer userId;private Integer deleteFlag;private LocalDateTime createTime;private LocalDateTime updateTime;}

用户列表

@Data
public class UserInfo {@TableId(value = "id",type = IdType.AUTO) //设置id为主键,并且自增private Integer id;private String userName;private String password;private String githubUrl;private Byte deleteFlag;private LocalDateTime createTime;private LocalDateTime updateTime;}

持久层 (Mapper)

我们使用的Mybatis-plus完成持久层的开发,创建mapper,记得实现BaseMapper

@Mapper
public interface BlogInfoMapper extends BaseMapper<BlogInfo>{
}
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}

实现博客列表

约定前后端的交互接口

客户端给服务端发送了/blog/getList请求,服务端给客户端返回了Json格式的数据

定义接口返回的实体

@JsonFormat注解,用于控制其中日期的格式

@Data
public class BlogInfoResponse {private Integer id;private String title;private String content;private Integer userId;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime updateTime;
}

实现图书列表的Controller类

@Slf4j
@RestController
@RequestMapping("/blog")
public class BlogInfoController {@Resource(name = "blogInfoServiceImpl")private BlogInfoService blogInfoService;@RequestMapping("/getList")public List<BlogInfoResponse> getList(){return blogInfoService.getList();}
}

Service采⽤接⼝对外提供服务,实现类⽤Impl的后缀与接⼝区别,好处是让代码更加灵活,起到了解耦的作用

图书列表的service类

public interface BlogInfoService {List<BlogInfoResponse> getList();
}
@Service
@Slf4j
public class BlogInfoServiceImpl implements BlogInfoService {@Autowiredprivate BlogInfoMapper blogInfoMapper;public List<BlogInfoResponse> getList() {List<BlogInfo> blogInfos=blogInfoMapper.selectList( //mybatis-plus的查询方法,用户查询满足以下条件的所有记录new LambdaQueryWrapper<BlogInfo>()  //创建一个条件构造器.eq(BlogInfo::getDeleteFlag, Constants.FLAG_NORMAL).orderByDesc(BlogInfo::getId));  //.eq:条件是//orderByDesc是将得到的id按desc(降序)排序//转化格式将BlogInfo格式转化为BlogInfoResponsereturn blogInfos.stream().map(blogInfo ->{   //.stream()将得到的博客列表变成一个数据流, .map对每一个数据进行转换BlogInfoResponse response=new BlogInfoResponse();  //创建一个新的对象BeanUtils.copyProperties(blogInfo,response);  //将blogInfo中的数据复制到response中,return response;                              // 这里的blogInfo指的是List<BlogInfo> blogInfos中的每一个数据}).collect(Collectors.toList());  //最后再将将 Stream 流结果收集成 List}
}

实现客户端的代码

使⽤ajax给服务器发送HTTP请求 

function getBlogList() {$.ajax({type: "get",url: "/blog/getList",success: function (result) {//针对后端的结果, 进行简单校验if (result.code == 200 && result.data != null && result.data.length > 0) {var finalHtml = "";for (var blogInfo of result.data) {finalHtml += '<div class="blog">';finalHtml += '<div class="title">' + blogInfo.title + '</div>';finalHtml += '<div class="date">' + blogInfo.createTime + '</div>';finalHtml += '<div class="desc">' + blogInfo.content + '</div>';finalHtml += '<a class="detail" href="blog_detail.html?blogId=' + blogInfo.id + '">查看全文&gt;&gt;</a>';finalHtml += '</div>'}$(".container .right").html(finalHtml);}}});} 

验证是否能正确的返回数据

http:127.0.0.1:8080/blog/getList 

返回结果正确 

实现博客详情

约定好前后端交互的接口

 

BlogController中写getBlogDetail方法,用于获得博客的详情

  @RequestMapping("/getBlogDetail")public BlogInfoResponse getBlogDetail(@NotNull Integer blogId){log.info("blogId:{}",blogId);return blogInfoService.getBlogDetail(blogId);}

记得添加参数校验的依赖 

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>

BlogInfoservice中添加getBlogInfo和getBlogDetail方法

将博客的详情转换为响应对象的原因:是因为数据库的实体类中很多的属性不适合直接给前端,所以定义了一些需要响应的数据,返回给前端

    BlogInfoResponse getBlogDetail(Integer blogId);BlogInfo getBlogInfo(Integer blogId);
@Overridepublic BlogInfoResponse getBlogDetail(Integer blogId) {return BeanTranUtils.tran(getBlogInfo(blogId));}@Overridepublic BlogInfo getBlogInfo(Integer blogId) {QueryWrapper<BlogInfo> queryWrapper=new QueryWrapper<>();queryWrapper.lambda().eq(BlogInfo::getDeleteFlag, Constants.FLAG_NORMAL).eq(BlogInfo::getId, blogId);return blogInfoMapper.selectOne(queryWrapper);}

 实现客户端的代码

使用location.search可得到blogId

        getBlogDetail();function getBlogDetail() {$.ajax({type: "get",url: "/blog/getBlogDetail" + location.search,success: function (result) {if (result.code == -1) {alert(result.errMsg);return;}if (result.code == 200 && result.data != null) {$(".content .title").text(result.data.title);$(".content .date").text(result.data.createTime);$(".content .detail").text(result.data.content);}}});}

进行验证 

返回结果正确 

实现登录 

使用令牌技术

用户登录进行登录,发送登录请求,经过负载均衡,将请求发给了一台服务器,服务器对其进行账户和密码的验证,验证成功后,生成一个令牌,返回给客户端,客户端收到令牌,将其储存起来,比如说储存在cookie中,用户登录成功后,进行其他功能的方法,此时请求将会发给另一台机器,机器验证其携带的令牌是否有效,如果有效,则说明用户登录成功,如果无效或者为空,则说明用户还未登录。

JWT令牌⽣成和校验

 引入JWT的依赖

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><!-- or jjwt-gson if Gson is preferred --><version>0.11.5</version><scope>runtime</scope></dependency>

生成令牌

首先生成一个密钥

@Testpublic void genKey(){Key key= Keys.secretKeyFor(SignatureAlgorithm.HS256);String encode= Encoders.BASE64.encode(key.getEncoded());System.out.println(encode);}

 生成JWT

我们可以在Https://jwt.io中验证生成的JWT是否正确 

约定博客详情的前后端交互的接口 

 

创建JWT⼯具类

public class JwtUtils {private static final Logger log = LoggerFactory.getLogger(JwtUtils.class);private static  String SECRET_STRING="2FDVor3CaEMpuG4vQ7T6BACe31F7cqPt09OSXEPQlGc=";private static  Key key=Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET_STRING));public static String genJwt(Map<String,Object> chaims){String jpt=Jwts.builder().setClaims(chaims).signWith(key).compact();return  jpt;}public static Claims parseToken(String token){if(!StringUtils.hasLength(token)){return null;}JwtParser build=Jwts.parserBuilder().setSigningKey(key).build();Claims claims=null;try{claims=build.parseClaimsJws(token).getBody();}catch (Exception e){log.error("token解析失败"+token);}return claims;}
}

创建请求的实体类

@Data
public class UserLoginRequest {@NotNull(message = "用户名不能为空")@Length(max =20)private String userName;@NotNull(message = "密码不能为空")@Length(min = 2,max =20 )private String password;}

创建响应的实体类

@AllArgsConstructor
@Data
public class UserLoginResponse {private Integer UserId;private String token;
}

实现Controller

@RequestBody注解的作用是返回的是Json对象

@Validated是用于触发参数校验的机制

因为在请求的实体类中使用了@NotNull注解和@Length注解

@Slf4j
@RestController
@RequestMapping("/user")
public class UserInfoController {@Resource(name = "userInfoServiceImpl")private UserInfoService userInfoService;@RequestMapping("/login")public UserLoginResponse userLogin(@RequestBody @Validated UserLoginRequest userLoginRequest){log.info("用户登录  用户:"+userLoginRequest.getUserName());return userInfoService.userLogin(userLoginRequest);}
}

 实现service

public interface UserInfoService {UserLoginResponse userLogin(UserLoginRequest userLoginRequest);
}

@Service
@Slf4j
public class UserInfoServiceImpl implements UserInfoService {@Autowiredprivate UserInfoMapper userInfoMapper;@Resource(name = "blogInfoServiceImpl")private BlogInfoService blogInfoService;@Overridepublic UserLoginResponse userLogin(UserLoginRequest userLoginRequest) {if(userLoginRequest==null){log.error("请求为空");throw new BlogException("请求为空");}QueryWrapper<UserInfo> queryWrapper=new QueryWrapper<>();queryWrapper.lambda().eq(UserInfo::getUserName,userLoginRequest.getUserName()).eq(UserInfo::getDeleteFlag, Constants.FLAG_NORMAL);UserInfo userInfo=userInfoMapper.selectOne(queryWrapper);if(userInfo==null){throw new BlogException("用户不存在");}if(!SecurityUtils.verify(userLoginRequest.getPassword(),userInfo.getPassword())){throw new BlogException("请输入正确的密码");}Map<String, Object> map=new HashMap<>();map.put("userId",userInfo.getId());map.put("userName",userInfo.getUserName());String token= JwtUtils.genJwt(map);return new UserLoginResponse(userInfo.getId(),token);}

 实现客户端的代码

 function login() {$.ajax({type:"post",url:"/user/login",contentType: "application/json",data:JSON.stringify({userName:$("#username").val(),password:$("#password").val()}),success:function(result){if(result!=null && result.code == 200){var response=result.data;if(!response.token || !response.userId){alert("登录响应数据不完整,请联系管理员");return;}localStorage.setItem("user_token",response.token);localStorage.setItem("loginUserId",response.userId);location.assign("blog_list.html")}else{alert("用户名或密码错误");return ;}}});}

ajax发⽣异常时,进⾏异常处理,公共处理,可以提取到 common.js 

$(document).ajaxError(function(event,xhr,options,exc){if(xhr.status==400){alert("参数校验失败");}});

实现强制要求登录

添加拦截器

@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String userToken=request.getHeader(Constants.USER_TOKEN_HEADER_KEY);if(userToken==null){response.setStatus(401);return false;}Claims claims= JwtUtils.parseToken(userToken);if(claims==null){response.setStatus(401);return false;}return true;}
}

 

@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/user/**","/blog/**").excludePathPatterns("/user/login");}
}

实现客户端的代码

前端请求时,header中统⼀添加token,后端返回异常时,跳转到登录⻚⾯

$(document).ajaxError(function(event,xhr,options,exc){if(xhr.status==401){location.assign("blog_login.html");}else if(xhr.status==400){alert("参数识别错误");}
});$(document).ajaxSend(function(e,xhr,opt){var user_token=localStorage.getItem("user_token");//从浏览器中的本地存储中得到令牌tokenxhr.setRequestHeader("user_token",user_token);//为即将发送的对象添加一个自定义请求头。
});

 实现显示用户信息

 

我们希望这个信息是根据用户登录的变化而变化

约定前后端交互接⼝

 

定义返回的实体类

@Data
public class UserInfoResponse {private Integer id;private String userName;private String githubUrl;
}

实现controller

@RequestMapping("/getUserInfo")public UserInfoResponse getUserInfo(@NotNull(message = "userId不能为空") Integer userId){return userInfoService.getUserInfo(userId);}@RequestMapping("/getAuthorInfo")public UserInfoResponse getBlogInfo(@NotNull(message = "blogId不能为空") Integer blogId){return userInfoService.getAuthorId(blogId);}

实现service 

    UserInfoResponse getUserInfo(Integer userId);UserInfoResponse getAuthorId(Integer blogId);

首先根据博客Id获得博客的信息,然后根据博客信息获得用户Id,最后根据用户的Id获得用户信息

@Overridepublic UserInfoResponse getUserInfo(Integer userId) {QueryWrapper<UserInfo> queryWrapper=new QueryWrapper<>();queryWrapper.lambda().eq(UserInfo::getDeleteFlag,Constants.FLAG_NORMAL).eq(UserInfo::getId,userId);UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);return BeanTranUtils.tran(userInfo);}@Overridepublic UserInfoResponse getAuthorId(Integer blogId) {//根据博客Id获得博客信息BlogInfo blogInfo = blogInfoService.getBlogInfo(blogId);if(blogInfo==null || blogInfo.getUserId()<=0){throw new BlogException("获取博客信息失败");}//根据博客信息获得作者信息return getUserInfo(blogInfo.getUserId());}

实现客户端的代码

由于在列表页面和详情页面都需要得到用户信息

所以我们将用户信息的代码提取common.js,直接在blog_list和blog_detail调用getUserInfo就可以

function getUserInfo(url){$.ajax({type:"get",url:url,success:function(result){if(result!=null && result.data!=null && result.code==200 ){var userInfo=result.data;$(".left .card h3").text(userInfo.userName);$(".left .card a").attr("href",userInfo.githubUrl);}}});
}

引入common.js

 <script src="js/common.js"></script>

进行调用 

 

实现用户退出 

只需要前端清除token就行,返回登录界面

注:因为也是好几个界面都可以进行用户退出,所以把这个代码也提取到common.js

function logout(){localStorage.removeItem("user_token");localStorage.removeItem("loginUserId");location.assign("blog_login.html");}

实现博客的发布 

约定好前后端交互的接口

定义请求的实体类

@Data
public class AddBlogRequest {@NotNull(message = "userId不能为空")private Integer userId;@NotBlank(message = "标题不能为空")private String title;@NotBlank(message = "内容不能为空")private String content;}

实现controller

    @RequestMapping("addBlog")public Boolean addBlog(@RequestBody @Validated AddBlogRequest addBlogRequest){return blogInfoService.addBlog(addBlogRequest);}

实现service

Boolean addBlog(AddBlogRequest addBlogRequest);
@Overridepublic Boolean addBlog(AddBlogRequest addBlogRequest) {BlogInfo blogInfo=new BlogInfo();BeanUtils.copyProperties(addBlogRequest,blogInfo);try {int result=blogInfoMapper.insert(blogInfo);if(result==1){return true;}return false;}catch (Exception e){log.error("发布错误,请联系管理员");throw new BlogException("发布错误,请联系管理员");}}

editor.md的介绍 

是一个开源的⻚⾯markdown编辑器组件

 

editor.md的使用

 

实现客户端的代码 

 $(function () {var editor = editormd("editor", {width: "100%",height: "550px",path: "blog-editormd/lib/"});}); function submit() {$.ajax({type:"post",url:"/blog/addBlog",contentType:"application/json",data:JSON.stringify({userId:localStorage.getItem("loginUserId"),title:$("#title").val(),content:$("#content").val()}),success: function(result){if(result!=null && result.code==200 && result.data==true){location.assign("blog_list.html");}else{alert("发布失败");}}});}

实现删除/编辑博客 

编辑博客

约定前后端交互的代码

实现修改博客的实体类

@Data
public class UpdateBlogRequest {@NotNull(message = "id不能为空")private Integer id;private String title;private String content;
}

实现controller

    @RequestMapping("/update")public Boolean updateBlog(@RequestBody @Validated UpdateBlogRequest updateBlogRequest){return blogInfoService.updateBlog(updateBlogRequest);}

 实现service

Boolean updateBlog(UpdateBlogRequest updateBlogRequest);
 @Overridepublic Boolean updateBlog(UpdateBlogRequest updateBlogRequest) {BlogInfo blogInfo=BeanTranUtils.tran(updateBlogRequest);try {int result = blogInfoMapper.updateById(blogInfo);return result==1;}catch (Exception e){log.error("编辑失败");throw new BlogException("内部错误,请联系管理员");}}

 

实现客户端代码

 editormd.markdownToHTML("detail", {markdown: result.data.content,});
//判断是否显示编辑/删除按钮let loginUserId = localStorage.getItem("loginUserId");if(result.data.userId==loginUserId){//当前作者是登录用户, 显示按钮let blogId = result.data.id;let finalHtml = '<button onclick="window.location.href=\'blog_update.html?blogId='+blogId+'\'">编辑</button>';finalHtml += '<button onclick="deleteBlog('+blogId+')">删除</button>';console.log(finalHtml);$(".content .operating").html(finalHtml);

删除博客 

约定前后端交互的代码

实现controller

@RequestMapping("/delete")public Boolean deleteBlog(Integer blogId){return blogInfoService.deleteBlog(blogId);}

实现service

删除一篇博客,并不是真正的从数据库中删除,而是将其delete_flag 修改为1

Boolean deleteBlog(Integer id);
@Overridepublic Boolean deleteBlog(Integer blogId) {BlogInfo blogInfo=new BlogInfo();blogInfo.setId(blogId);blogInfo.setDeleteFlag(Constants.FLAG_DELETE);try {int result = blogInfoMapper.updateById(blogInfo);return result==1;}catch (Exception e){log.error("删除失败");throw new BlogException("内部错误,请联系管理员");}}

 实现客户端代码

function deleteBlog(blogId) {$.ajax({type: "post",url: "/blog/delete?blogId=" + blogId,success: function(result){if(result.code ==200 && result.data==true){//删除成功location.href = "blog_list.html";}else{alert("删除失败");}}});alert("删除博客");}

 加密/加盐

 目前我们的密码还是明文显示的,为了保护我们的密码,所以要对其加密

 密码算法主要分为三类:对称密码算法,非对称密码算法,摘要算法

在博客系统中,我们采用MD5进行加密

采用密码拼接一个随机的字符进行加密,随机的字符我们称之为“盐”

加密的原理

检验的原理

加密工具类

public class SecurityUtils {public static String encrypt(String inputPassword){String salt= UUID.randomUUID().toString().replace("-","");String encryptPassword= DigestUtils.md5DigestAsHex((salt+inputPassword).getBytes(StandardCharsets.UTF_8));return salt+encryptPassword;}public static Boolean verify(String inputPassword,String sqlPassword){if(!StringUtils.hasLength(inputPassword)){return false;}if(sqlPassword==null  || sqlPassword.length()!=64){return false;}String salt=sqlPassword.substring(0,32);String encryptPassword=DigestUtils.md5DigestAsHex((salt+inputPassword).getBytes(StandardCharsets.UTF_8));String result=salt+encryptPassword;return sqlPassword.equals(result);}}

注:记得将数据库中的密码改为加密后的密码

自此项目结束,大家可以把这个项目部署到云服务器上

上传服务记得勾选prod,然后刷新Maven

希望能对大家有所帮助!!!!

相关文章:

  • 基于 Spring Boot 瑞吉外卖系统开发(十三)
  • Vxworks 系统详解
  • 装饰器在Python中的作用及在PyTorchMMDetection中的实战应用
  • 我国城市轨道交通行业人工智能大模型发布,迈向智慧化新征程​
  • 本地的ip实现https访问-OpenSSL安装+ssl正式的生成(Windows 系统)
  • Java【10_1】用户注册登录(面向过程与面向对象)
  • tomcat搭建内网论坛
  • 【论信息系统项目的资源管理】
  • docker大镜像优化实战
  • 专题三:穷举vs暴搜vs深搜vs回溯vs剪枝(全排列)决策树与递归实现详解
  • 企业如何构建安全高效的数据合规体系?
  • python使用OpenCV 库将视频拆解为帧并保存为图片
  • 问题及解决02-处理后的图像在坐标轴外显示
  • 用自写的jQuery库+Ajax实现了省市联动
  • c++STL-list的模拟实现
  • MyBatis 批量新增与删除功能完整教程
  • python_竞态条件
  • windowsC++操作ADB
  • ansible进阶版01
  • js中的同步方法及异步方法
  • 金正恩观摩朝鲜人民军各兵种战术综合训练
  • 长三角议事厅·周报|从模速空间看上海街区化AI孵化模式
  • 《审判》|“被告”的魅力:K在等什么?
  • 外交部:中方期待印巴巩固和延续停火势头,避免冲突再起
  • 中美经贸高层会谈在瑞士日内瓦举行
  • 首映丨纪录电影《滚烫年华》:献给所有奋斗者