day01-项目介绍&功能实现
项目介绍&功能实现
1.项目介绍&环境搭建
一个以社交平台为核心的轻电商项目,功能如下:
短信登录、商户查询缓存、优惠券秒杀、达人探店、好友关注、附近的商户、用户签到、UV统计

1.1项目架构

1.2项目环境搭建
1.2.1后端项目搭建
mysql的版本采用5.7及以上版本
(1)首先创建数据库,需要创建的表有:
- tb_user:用户表
- tb_user_info:用户详情表
- tb_shop:商品信息表
- tb_shop_type:商品类型表
- tb_blog:用户日记表(达人探店日记)
- tb_follow:用户关注表
- tb_voucher:优惠券表
- tb_voucher_order:优惠券的订单表
(2)导入项目依赖
<dependencies>
<!--SpringBoot整合的redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--数据库连接池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--web-starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>5.1.47</version>
</dependency>
<!--Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--springboot-test-starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--mybatisplus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.17</version>
</dependency>
</dependencies>
(3)application.yml配置文件
server:
port: 8081
spring:
application:
name: hmdp
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/hmdp?useSSL=false&serverTimezone=UTC
username: root
password: 123456
redis:
host: 192.168.1.3
port: 6379
password: 123456
lettuce:
pool:
max-active: 10
max-idle: 10
min-idle: 1
time-between-eviction-runs: 10s
jackson:
default-property-inclusion: non_null # JSON处理时忽略非空字段
mybatis-plus:
type-aliases-package: com.hmdp.entity # 别名扫描包
logging:
level:
com.hmdp: debug
1.2.2部署前端项目
这里部署已经实现好的前端项目
(1)解压文件:

(2)使用CMD窗口启动 nginx.exe:

(3)打开谷歌浏览器---右键页面,点击“检查”--地址栏访问 127.0.0.1:8080,即可看到页面:

(4)若要退出Nginx服务,只需在目录下运行命令 nginx -s quit

参考:Nginx关闭方法
2.功能01-短信登录
2.1基于Session实现登录
2.1.1思路分析

2.1.2代码实现
2.1.2.1发送短信验证码

发送短信验证码:

发送验证码的接口为:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手机号>
请求方式:POST
请求路径:/user/code
请求参数:phone,电话号码
返回值:无
部分代码
(1)User类、UserDTO类(DTO类主要是为了存储非敏感数据)
package com.hmdp.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 用户实体类
*
* @author 李
* @version 1.0
*/
@Data
@EqualsAndHashCode(callSuper = false)
//chain = true,则对应字段的setter方法调用后,会返回当前对象
@Accessors(chain = true)
@TableName("tb_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
//用户id(主键)
@TableId(value = "id", type = IdType.AUTO)
private Long id;
//手机号码
private String phone;
//密码
private String password;
//昵称
private String nickName;
//用户头像
private String icon = "";
//创建时间
private LocalDateTime createTime;
//更新时间
private LocalDateTime updateTime;
}
package com.hmdp.dto;
import lombok.Data;
@Data
public class UserDTO {
private Long id;
private String nickName;
private String icon;
}
(2)UserMapper
package com.hmdp.mapper;
import com.hmdp.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* Mapper 接口
*
* @author 李
* @version 1.0
*/
public interface UserMapper extends BaseMapper<User> {
}
(3)IUserService
package com.hmdp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.entity.User;
import javax.servlet.http.HttpSession;
/**
* 服务类
*
* @author 李
* @version 1.0
*/
public interface IUserService extends IService<User> {
Result sendCode(String phone, HttpSession session);
}
(4)UserServiceImpl.java
package com.hmdp.service.impl;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.Result;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RegexUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpSession;
/**
* 服务实现类
*
* @author 李
* @version 1.0
*/
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public Result sendCode(String phone, HttpSession session) {
//1.校验手机号
boolean phoneInvalid = RegexUtils.isPhoneInvalid(phone);
//2.如果不符合,返回错误信息
if (phoneInvalid) {//true表示不符合格式
return Result.fail("手机号格式错误!");
}
//3.符合,生成验证码
String code = RandomUtil.randomNumbers(6);//生成6位数的随机号码
//4.保存验证码
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
//5.发送验证码(由于这里涉及到一些其他方的工具,先不做)
log.debug("发送短信验证码成功,验证码={}", code);
//6.返回OK
return Result.ok();
}
}
(2)UserController.java
package com.hmdp.controller;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.entity.UserInfo;
import com.hmdp.service.IUserInfoService;
import com.hmdp.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
/**
* 前端控制器
*
* @author 李
* @version 1.0
*/
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private IUserService userService;
/**
* 发送手机验证码
* @param phone 用户输入的手机号码
* @param session session域
* @return
*/
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
//发送短信验证码并保存验证码
return userService.sendCode(phone,session);
}
}
测试:输入手机号,点击发送验证码,后台输出如下:


2.1.2.2短信验证码登录/注册


当点击登录的时候,前端发送的请求如下:

发送验证码的接口为:http://127.0.0.1:8080/api/user/login
请求方式:POST
请求路径:/user/login
请求参数:phone,code
返回值:无
部分代码
(1)修改IUserService.java,声明login()方法
package com.hmdp.service;
import ...
/**
* 服务类
*
* @author 李
* @version 1.0
*/
public interface IUserService extends IService<User> {
Result login(LoginFormDTO loginForm, HttpSession session);
}
(3)修改UserServiceImpl.java,实现login()
package com.hmdp.service.impl;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RegexUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpSession;
import static com.hmdp.utils.SystemConstants.USER_NICK_NAME_PREFIX;
/**
* 服务实现类
*
* @author 李
* @version 1.0
*/
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
//...
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//1.校验手机号
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
//如果不符合,返回错误信息
return Result.fail("手机号格式错误!");
}
//2.校验验证码
Object cacheCode = session.getAttribute("code");
String code = loginForm.getCode();
if (cacheCode == null || !cacheCode.toString().equals(code)) {
//cacheCode == null 说明没有发过验证码,因为session里面没有存放
//!cacheCode.toString().equals(code) == true
//说明用户输入的验证码和session的不一致
return Result.fail("验证码错误");
}
//3.根据手机号查询用户是否已经注册
User user = query().eq("phone", phone).one();
if (user == null) {
//若不存在,则在创建用户
user = createUserWithPhone(phone);
}
//4.保存用户到session(这里只保存id、昵称、手机号)
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
return Result.ok();
}
private User createUserWithPhone(String phone) {
//1.创建用户
User user = new User();
//设置手机号
user.setPhone(phone);
//设置昵称,初始值为随机的15位字符串
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
//2.保存用户到DB
save(user);
return user;
}
}
(3)修改UserController
/**
* 登录功能
* @param loginForm 登录参数:包含手机号+验证码,或手机号+密码
* @param session session域
*/
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
//实现登录功能
return userService.login(loginForm,session);
}
(4)测试:前端输入手机号码,和验证码,点击登录

后台输出:


2.1.2.3校验登录状态


以上的逻辑放到拦截器去实现,并使用ThreadLocal去保存用户信息,实现线程安全。
(1)创建拦截器 LoginInterceptor
package com.hmdp.interceptor;
import cn.hutool.http.HttpStatus;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import com.hmdp.utils.UserHolder;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* @author 李
* @version 1.0
* 登录拦截器
*/
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.获取session中的用户
HttpSession session = request.getSession();
UserDTO userDTO = (UserDTO) session.getAttribute("user");
//2.判断用户是否登录过
if (userDTO == null) {
//如果没有登录过,拦截,返回状态码-401
response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
return false;//拦截
}
//3.如果存在,保存用户到ThreadLocal
UserHolder.saveUser(userDTO);//这里是一个工具类,自动创建ThreadLocal
ThreadLocal<UserDTO> userThreadLocal = new ThreadLocal<UserDTO>();
userThreadLocal.set(userDTO);
//4.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用户(根据当前线程,移除用户信息)
/**
* 为什么要移除用户?
* 因为ThreadLocal对应的是一个线程的中数据
* 每次http请求,tomcat都会创建一个新的线程
* 但是一次http请求结束后,如果web容器使用了线程池(线程被重复利用),
* 问题一:ThreadLocal的生命周期不等于一次Request的生命周期,造成获取threadLocal内数据异常
* 问题二:内存溢出,ThreadLocal依赖没有释放,无法GC
* 因此,需要手动移除数据
*/
UserHolder.removeUser();
}
}
(2)配置拦截器 MvcConfig
package com.hmdp.config;
import com.hmdp.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author 李
* @version 1.0
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns().
excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/login",
"/user/code"
);
}
}
(3)修改UserController,获取当前登录用户信息,并返回
/**
* @return 返回当前登录用户的信息
*/
@GetMapping("/me")
public Result me() {
//获取当前登录的用户并返回
UserDTO user = UserHolder.getUser();
return Result.ok(user);
}
(4)测试:登录用户

后台输出:

返回的响应:

前端成功显示登录后的用户信息:

2.2集群的session共享问题
集群的Session共享问题:多台Tomcat并不共享它们之间的Session存储空间,如果有多台tomcat服务器,当请求切换到不同tomcat服务时,会导致数据丢失的问题。
(1)问题具体分析:

如上,当请求进入Nginx时,Nginx会做一个负载均衡(一般是对tomcat集群进行轮询)。
假设用户的请求第一次被负载均衡到了tomcat_1,例如之前的登录验证,那么tomcat_1就会将信息保存到它的session域中;
如果用户第二次的请求被负载均衡到了tomcat_2,当tomcat_2要去获取验证码或者用户信息的时候,由于多台tomcat不能共享它们之间的Session存储空间,导致tomcat_2不能获取存储在tomcat_1的session数据,表现出来就是数据丢失的问题。
(2)解决方案:

如图,session的替代方案必须满足:数据共享、内存存储(快)、k-v结构(方便存/取数据)。
如果使用redis,则刚好可以满足这些需求:
首先,redis是在tomcat之外的一个存储方案,所有的tomcat都可以访问到,解决了数据共享问题;
其次,redis是内存存储,性能很强,读写很快,读写延时基本上是在微秒级别;
最后,redis是k-v结构,方便数据的存取。
综上,我们下面将会使用redis来实现session登录。
2.3基于Redis实现共享session登录
2.3.1思路分析

(1)发送短信验证码:
如左图所示,当用户发送短信验证码时,将生成的验证码存入到redis中,为了让一个手机号码对应一个验证码,这里使用手机号作为key,value则是验证码。(使用Redis-String类型)
(2)短信验证码登录、注册:
使用redis代替之后,我们在获取短信验证码的时候,也使用手机号为key去取验证码,这就解决了发送和登录手机号可能不一致的问题。如果需要保存用户信息,可以使用Redis-Hash类型。
(3)登录状态校验:

session机制:之前直接使用session存储用户信息后,tomcat会自动给浏览器返回一个cookie(包含jsessionid),每次向服务端发送请求时,都会自动携带。这样tomcat根据jsessionid,就能自动找到session,也就能找到session里的数据。
登录成功后,会存储一个token到redis中作为key,value就是用户信息。我们可以模仿jsessionid,存储token到redis时,同时将token放到cookie中,返回给客户端,之后每次客户端发送请求,都会携带token,服务器就可以获取token,到redis去获取用户信息,从而进行一系列业务流程。
登录凭证token保存在前端浏览器:前端代码会存储token到SessionStorage中,设置request拦截器,将用户token放入请求头中。这样,每次发送请求的时候都会使用token作为请求头。我们在服务端就可以获取token数据校验。
2.3.2代码实现
2.3.2.1发送短信验证码
在2.1代码的基础上修改
修改UserServiceImpl.java的sendCode()方法
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result sendCode(String phone, HttpSession session) {
//1.校验手机号
boolean phoneInvalid = RegexUtils.isPhoneInvalid(phone);
//2.如果不符合,返回错误信息
if (phoneInvalid) {//true表示不符合格式
return Result.fail("手机号格式错误!");
}
//3.符合,生成验证码
String code = RandomUtil.randomNumbers(6);//生成6位数的随机号码
//4.保存验证码到 redis中,这里的 key 最好使用业务前缀作为区分
// set key value ex
stringRedisTemplate.opsForValue()
.set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
//5.发送验证码(由于这里涉及到一些其他方的工具,先不做)
log.debug("发送短信验证码成功,验证码={}", code);
//6.返回OK
return Result.ok();
}
2.3.2.2短信验证码登录/注册
修改UserServiceImpl.java的login()方法
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//1.校验手机号
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
//如果不符合,返回错误信息
return Result.fail("手机号格式错误!");
}
//2.从redis获取验证码并校验
String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
String code = loginForm.getCode();
if (cacheCode == null || !cacheCode.equals(code)) {
return Result.fail("验证码错误");
}
//3.根据手机号查询用户是否已经注册
User user = query().eq("phone", phone).one();
if (user == null) {
//若不存在,则在创建用户
user = createUserWithPhone(phone);
}
//4.保存用户到redis中 (这里只保存id、昵称、手机号)
//随机生成token,作为登录令牌
String token = UUID.randomUUID().toString(true);
//将user对象转为redis-hash
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
//使用工具类转(指定转换后的数据类型为String)
Map<String, Object> map = BeanUtil.beanToMap(userDTO, new HashMap<>(),
CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
//存储到redis中,设置有效期为30分钟
stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token, map);
stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.MINUTES);
//5.将token返回客户端
return Result.ok(token);
}
2.3.2.3校验登录状态
(1)修改LoginInterceptor.java
在上述代码中,我们设置了token在redis中的过期时间。但是和session不同,redis的过期时间是绝对时间,即在设定的时间内,无论有没有使用到token,从设定的那一刻起一直到第30分钟,该数据都会被删除。
因此,我们需要在拦截器中重新设置redis的token信息:每当用户有操作时,拦截器就重新设置过期时间。
package com.hmdp.interceptor;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import com.hmdp.dto.UserDTO;
import com.hmdp.utils.UserHolder;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_KEY;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_TTL;
/**
* @author 李
* @version 1.0
* 登录拦截器
*/
public class LoginInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
//不能使用注解@Component将拦截器注入到spring容器中!!
//因为拦截器是在spring容器初始化之前执行的
//因此这里使用构造器设置,在注册拦截器的时候去注入
public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.获取请求头中的token
String token = request.getHeader("authorization");
//如果浏览器的token为空
if (StrUtil.isBlank(token)) {
//拦截,返回状态码-401
response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
return false;//拦截
}
//2.基于token获取redis中的用户
String key = LOGIN_USER_KEY + token;
Map<Object, Object> userMap =
stringRedisTemplate.opsForHash().entries(key);
//3.判断用户是否存在
if (userMap.isEmpty()) {
//拦截,返回状态码-401
response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
return false;//拦截
}
//4.如果存在,将查询到的Hash数据转为UserDTO对象
UserDTO userDTO =
BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
//5.然后保存到ThreadLocal
UserHolder.saveUser(userDTO);
//6.刷新token有效期(30mins)
stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);
//7.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用户(根据当前线程,移除用户信息)
UserHolder.removeUser();
}
}
(2)修改配置类 MvcConfig.java
不能使用注解@Component将拦截器注入到spring容器中!!因为拦截器是在spring容器初始化之前执行的,因此这里使用构造器设置,在注册拦截器的时候去注入

2.3.2.4测试
(1)启动redis服务器,重启项目。
(2)输入手机号,输入验证码,点击登录

(3)成功登录,显示个人主页

(4)redis存储的数据如下:
短信验证码:

token:

测试通过。
2.3.3总结
(1)基于Redis实现实现短信登录改造的点:
发送短信验证码时,把短信验证码作为value,使用手机号作为key,存入redis中。
确保了:a.每个手机号都有唯一的验证码;b.用户登录时可以基于手机号来获取验证码,从而实现验证
短信登录时,保存用户信息到redis(key-token,value-用户信息),同时返回token给用户浏览器,保存在浏览器的SessionStorage。每次校验登录状态,都会从中取出token进行校验。
(2)Redis代替Session需要考虑的问题:
- 选择合适的数据结构
- 选择合适的key
- 选择合适的存储粒度
day01-项目介绍&功能实现的更多相关文章
- Android Hotpatch系列之-项目介绍
给现实Android apk打补丁,不用强迫客户升级客户端,悄悄的就把bug修复了,程序猿再也不用被老大骂娘了. 客户端例子实现:https://github.com/fengcunhan/Hotpa ...
- 揭开webRTC媒体服务器的神秘面纱——WebRTC媒体服务器&开源项目介绍
揭开webRTC媒体服务器的神秘面纱--WebRTC媒体服务器&开源项目介绍 WebRTC生态系统是非常庞大的.当我第一次尝试理解WebRTC时,网络资源之多让人难以置信.本文针对webRTC ...
- windows下nodejs express安装及入门网站,视频资料,开源项目介绍
windows下nodejs express安装及入门网站,视频资料,开源项目介绍,pm2,supervisor,npm,Pomelo,Grunt安装使用注意事项等总结 第一步:下载安装文件下载地址: ...
- [转]基于C#的开源GIS项目介绍之SharpMap篇
我是一个刚毕业的GIS本科毕业生,目前在杭州从事GIS软件应用开发.在项目开发中总感觉自己的编程水平还不够,于是想找些开源GIS小项目来研究研究,借以提高自己的编程能力和项目开发能力.在网上搜了一下“ ...
- OpenLayers项目分析——(一)项目介绍
OpenLayers 是由MetaCarta公司开发的,用于WebGIS客户端的JavaScript包,目前的最高版本是2.7 V,通过BSD License 发行.它实现访问地理空间数据的方法都符合 ...
- LVS项目介绍
LVS项目介绍 章文嵩 (wensong@linux-vs.org) 转自LVS官方参考资料 2002 年 3 月 本文介绍了Linux服务器集群系统--LVS(Linux Virtual Serve ...
- Android项目实战之高仿网易云音乐项目介绍
这一节我们来讲解这个项目所用到的一些技术,以及一些实现的效果图,让大家对该项目有一个整体的认识,推荐大家收藏该文章,因为我们发布文章后会在该文章里面加入链接,这样大家找着就很方便. 目录 第1章 前期 ...
- OpenDaylight开发hello-world项目之功能实现
OpenDaylight开发hello-world项目之开发环境搭建 OpenDaylight开发hello-world项目之开发工具安装 OpenDaylight开发hello-world项目之代码 ...
- WPF 开源框架项目介绍
旧版本项目说明 旧版本由于是从学习WPF进行开发的, 历经时长有半年之余,基本上现学现用的那种, 所以存在很多缺陷, 由于整体的设计多处更新, 故旧版本将会终止维护(砍), 基于WCF的项目也会停止, ...
- WeChair Plus版项目介绍
这个作业属于哪个课程 软件工程 (福州大学至诚学院 - 计算机工程系) 团队名称 WeChair 这个作业要求在哪里 团队作业第一次:团队作业第一次(2) 这个作业的目标 项目介绍,项目修改 作业正文 ...
随机推荐
- 【技术指北】通过SMB协议在iOS和Windows之间传输文件
windows操作 新建文件夹 设置文件夹的属性,选择共享 设置高级共享,权限选择完全控制 选择共享,选择Everyone cmd - ipcofig,获取ipv4地址 iOS操作 打开文件管理器 选 ...
- idea的tomcat控制台输出乱码
tomcat乱码问题 idea的tomcat控制台输出乱码 找到自己的安装目录 用vscode打开 ctrl+f打开搜索 输入encoding 最后一个是用来给idea中的控制台输出 --->改 ...
- liunx密码破解
重启系统后出现GRUB界面在引导装载程序菜单上,用上下方向键选择你忘记密码的那个系统键入"e" 来进入编辑模式.进入"编辑模式"之后用上下方向键上下移动光标,找 ...
- vue使用阿里oss上传
1.首先用包管理工具 npm install ali-oss --S 下载oss依赖包 2.在util文件里创建util.js文件,在该文件写入 export default { getClient: ...
- 基本的dns命令
打开cmd的方式 win+r 键 输入cmd 管理员方式运行 打开桌面 命令提示符 盘符切换 直接输入要切换的盘 查看当前目录下所有文件 dir 切换目录 cd /d 跨盘 ...
- Google colab复现openpose+st-gcn的心路历程
Google colab复现openpose+st-gcn的心路历程: 因为本人的大致研究方向为动作识别,加上自己的笔记本比较菜鸡,没有牛皮的显卡,所以采用了google的colab.在网上查了三天, ...
- Android笔记--选择按钮
复选框CheckBox 具体实现: 注意: 开关按钮switch 具体实现: 单选按钮RadioButton 需要放到radioGroup里面写单选按钮 具体实现:
- PicList 现已上架Mac App Store 分享下整个上架过程和遇到的问题
PicList 是一款云存储/图床平台管理和文件上传工具,基于 PicGo 进行了深度二次开发,保留了 PicGo 的所有功能的同时,为相册添加了同步云端删除功能,同时增加了完整的云存储管理功能,包括 ...
- University of Toronto Faculty of Arts and Science MAT344– Final Assessment Combinatorics Instructors: Stanislav Balchev and Max Klambauer 19 August 2020
目录 随便找的一份测试题 T7 T9 T6 T5 solution to (a) solution to (b) solution to (c) solution to (d) T1 T2 T3 T4 ...
- Vditor在原生JS中如何结合后端使用
目录 1.Vditor介绍 2.如何在原生JS中结合后端使用 2.1 背景 2.2 正确使用方式 2.2.1 编辑页面 2.2.2 回显页面(修改页面) 2.2.3 预览页面 3.小结一下 1.Vdi ...