苍穹外卖学习笔记

发布于:2024-12-06 ⋅ 阅读:(44) ⋅ 点赞:(0)

文章目录

软件开发整体介绍

设计流程

  • 需求分析,需求规格说明书、产品原型
  • 设计,UI设计、数据库设计、接口设计
  • 编码,项目代码、单元测试
  • 测试,测试用例、测试报告
  • 上线运维,软件环境安装、配置
    分工
    项目经理:对整个项目负责,任务分配、把控进度
    产品经理:进行需求调研,输出需求调研文档、产品原型等
    UI设计师:根据产品原型输出界面效果图
    架构师:项目整体架构设计、技术选型等
    开发工程师:代码实现
    测试工程师:编写测试用例,输出测试报告
    运维工程师:软件环境搭建、项目上线

苍穹外卖项目介绍

分为前端(管理端和用户端),后端(java)
管理端(Web)

  • 员工管理
  • 分类管理
  • 菜品管理
  • 套餐管理
  • 订单管理
  • 工作台
  • 数据统计
  • 来电提醒
    用户端(微信小程序)
  • 微信登录
  • 商品浏览
  • 购物车
  • 用户下单
  • 微信支付
  • 历史订单
  • 地址管理
  • 用户催单

后端环境搭架

父项目中的子模块

  • sky-common,公共类,供其他模块使用
  • sky-pojo,ebtity\DTO\VO
  • sky-server,拦截器\controller\service\mapper\启动类等

git版本控制

  • 创建git本地仓库
  • 创建git远程仓库
  • 将本地文件推送到git远程仓库中(gitee)
    .gitignore是git提交忽略配置文件

数据库搭建

前后端联调

为什么前端请求地址和后端访问地址不相同,还可以访问到,为什么要这么做(反向代理)?
nginx反向代理(好处)

  • 提高访问速度(后端数据可以在nginx上缓存)
  • 进行负载均衡
  • 保证后端服务安全

登录功能完善-MD5加密

123456->dhaskdbcaskcuywia(MD5算法不可逆)
// md5加密,然后再进行比对
password = DigestUtils.md5DigestAsHex(password.getBytes());

接口文档

yapi导入json接口文档

swagger

介绍和使用方式

Knife4j,为javaMVC框架集成swagger生成Api文档的增强解决方案

  • swagger,扫描controller类,也就是根据在后端已经实现的接口生成接口文档
  • 可通过’http://localhost:8080/doc.html#/home’类似地址查看当前实现接口,
  • 还能做接口测试,一定程度上可替代postman
    和Yapi不冲突,设计阶段工具,管理维护接口,而swagger是开发阶段使用的框架,帮助开发人员做后端的接口测试
/**
 * 配置类,注册web层相关组件
 */
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

    @Autowired
    private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;

    /**
     * 注册自定义拦截器
     *
     * @param registry
     */
    protected void addInterceptors(InterceptorRegistry registry) {
        log.info("开始注册自定义拦截器...");
        registry.addInterceptor(jwtTokenAdminInterceptor)
                .addPathPatterns("/admin/**")
                .excludePathPatterns("/admin/employee/login");
    }

    /**
     * 通过knife4j生成接口文档
     * @return
     */
    @Bean
    public Docket docket() {
        log.info("准备生成接口文档");
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("苍穹外卖项目接口文档")
                .version("2.0")
                .description("苍穹外卖项目接口文档")
                .build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }

    /**
     * 设置静态资源映射
     * @param registry
     */
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始设置静态资源映射");
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}

常用注解

让swagger生成的Api文档拥有更好的可读性

  • @Api,用在Controller等类上
  • @ApiModel,用在entity\DTO\VO等类上
  • @ApiModelProperty,用在属性上,描述属性信息
  • @ApiOperation,用在方法上,说明方法的用途作用

新增员工

需求分析

项目约定

  • 管理端发出的请求,统一用/admin作为前缀
  • 用户端发出的请求,统一用/user作为前缀

功能完善

  • 重复新增全局异常捕获
  • 记录创建和修改人id
    同一次请求在服务端同属一个线程

分页查询

代码开发

PageHelper的底层原理也是通过localthread线程的原理将页码和页面大小从service层传到mapper层
复杂的查询需求写在映射文件xml中
alt+enter\alt+insert两个常用的代码生成快捷键

功能完善

  • 对返回给前端的日期格式化
    /**
     * 消息转换器
     * @param converters
     */
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        log.info("扩展消息转换器");
        //创建一个消息转换器对象
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        //需要为转换器设置一个对象转换器,对象转换器可以将java对象序列化为json数据
        converter.setObjectMapper(new JacksonObjectMapper());
        //将自己的消息转化器加入到容器中,0代表优先使用,转换器排在第一个使用
        converters.add(0,converter);

启用禁用员工账号

Employee类上有@Builder注解,可以通过以下方式实体类构造

//实体类构造器
Employee employee = Employee.builder()
		.status(status)
		.id(id)
		.build();

编辑员工信息

根据id回显员工信息

编辑员工信息

接口调试时返回为空可能是token令牌过期

分类管理模块(菜品和套餐分类)

具体操作与员工管理的代码较为相似,不再赘述
新增模块(复制粘贴过来的新文件,手写的是会自动编译的)记得编译compile

公共字段自动填充

枚举(enum)是一种特殊的类

/**
 * 数据库操作类型
 */
public enum OperationType {
    /**
     * 更新操作
     */
    UPDATE,
    /**
     * 插入操作
     */
    INSERT
}

自定义注解用于加在mapper中的方法上

@Target(ElementType.METHOD)//指定此注解只能用在方法上
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型,UPDATE,INSERT
    OperationType value();



}

使用AOP机制,为创建人\创建时间\修改人\修改时间设定值

/**
 * 自定义切面,实现公共字段自动填充处理逻辑
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
    /**
     * 切入点
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut(){}

    /**
     * 前置通知,在通知中进行公共字段的赋值
     * @param joinPoint
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint){
        log.info("开始公共字段自动填充");
        //获取到当前被拦截的方法上的数据库操作类型
        //方法签名对象
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取方法上的注解对象
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
        //获取数据库操作类型
        OperationType operationType = autoFill.value();

        //获取到当前被拦截的方法的参数--实体对象
        Object[] args = joinPoint.getArgs();
        if(args == null || args.length == 0){
            return;
        }
        Object entity = args[0];

        //准备赋值的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        //根据当前不同的操作类型(update和insert),为对应的属性通过反射来赋值
        if(operationType == OperationType.INSERT){
            //为4个公共字段赋值
            try{
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME,LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER,Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME,LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER,Long.class);

                //通过反射为对象属性赋值
                setCreateTime.invoke(entity,now);
                setCreateUser.invoke(entity,currentId);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            }
            catch(Exception e){
                e.printStackTrace();
            }
        }
        else if(operationType == OperationType.UPDATE){
            //为4个公共字段赋值
            try{
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME,LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER,Long.class);

                //通过反射为对象属性赋值
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            }
            catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}

最后需要在特定方法上添加注解,使之添加对应的通知处理

@AutoFill(value = OperationType.UPDATE)
void update(Employee employee);

新增菜品

  • 这里要实现向菜品表中插入1条数据,向口味表中插入n条数据
  • 注意如何获取菜品表中的主ID插入到口味表中

菜品分页查询

  • 还是要注意pagehelper如何使用
  • 注意要如何从分类表中获取分类名称.SQL语句要怎么写

删除菜品

  • 共要操作三张表,菜品表,口味表,菜品和套餐的中间关系表

修改菜品

  • 注意不仅要修改菜品表,还要修改口味表

Redis

Redis是一个基于内存的key-value结构数据库
官网,www.redis.net.cn

  • 基于内存存储,读写性能高
  • 适合存储热点数据(热点商品、资讯、新闻)
  • 企业应用广泛
    redis属于绿色软件(文件夹),解压即可使用
    启动redis服务
    cmd中启动redis-server.exe
    客户端(这里的客户指的是开发人员)使用服务
    cmd中启动redis-cli.exe
    命令示例,redis-cli.exe -h localhost -p 6379 -a 123456
    -h 地址 -p 端口号 -a 密码
    密码需要在redis.windows-service.conf手动设置
    虽然可以在命令行使用redis,但一般还是图形界面的redis更好用更主流(还是要手动启动redis服务的)

常用数据类型

key为字符串类型,value有5种常用的数据类型

  • 字符串string
  • 哈希hash ,类似HashMap
  • 列表list ,类似LinkedList
  • 集合set ,类似HashSet
  • 有序集合sorted set/zset 集合中每个元素关联一个分数score,根据分数升序

字符串操作命令

  • SET key value ,设置指定key的值
  • GET key ,获取指定key的值
  • SETEX key seconds value ,设置指定key值,并将key的过期时间设置为sencond秒
  • SETNX key value ,只有在key不存在时,设置key的值

哈希操作命令

  • HSET key field value,设置值
  • HGET key field ,获取值
  • HDEL key field,删除值
  • HKEYS key,获取表中所有字段
  • HVALS key,获取表中所有值

列表操作命令

  • LPUSH key value1 [value],将一个或多个值插入头部(后插入的那端为头部)
  • LRANGE key start stop,获取列表指定范围的元素
  • RPOP key,移除并获取列表最后一个元素,也就是第一个被插入的
  • LLEN KEY,获取列表长度

集合操作命令

  • SADD key mamber1 [member2],向集合添加一个或多个成员
  • SMEMBER key,返回集合中的所有成员
  • SCARD key,获取集合的成员数
  • SINTER key1 [key2],返回给定集合的交集
  • SUNION key1 [key2],返回给定集合的并集
  • SREM key member1 [member2],删除集合中的一个或多个成员

有序集合操作命令

每个元素关联一个double类型的分数

  • ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员
  • ZRANGE key start stop [WITHSCORS],通过索引区间返回指定区间的成员
  • ZINCRBY key increment member,添加上增量increment
  • ZREM key member [member…],移除集合中的一个或多个成员

通用命令

  • KEYS pattern ,查找所有符合给定模式的key
  • EXISTS key, 检查给定key是否存在
  • TYPE key , 返回key所存储的值的类型
  • DEL key ,该命令用于在key存在时,删除key

在java中操作redis,Spring Data Redis

使用方法

  1. 导入spring data redis 的maven坐标
  2. 配置redis数据源
  3. 编写配置类,创建redistemplate对象
  4. 通过redistemplate对象操作redis
    maven坐标
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置redis数据源,application.yml文件

  redis:
    host: localhost
    port: 6379
    password: 123456
    database: 10

编写配置类

@Configuration
@Slf4j
public class RedisConfiguration {
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        log.info("redisTemplate,开始创建redis模板对象...");
        RedisTemplate redisTemplate =new RedisTemplate();
        //设置redis的连接工厂对象
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //设置redis的序列化器
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

操作字符串数据

@Autowired
private RedisTemplate redisTemplate;
public void testString(){
	//set get setex setnx
	redisTemplate.opsForValue().set("name","xiaoming");
	System.out.println((String) redisTemplate.opsForValue().get("name"));
	redisTemplate.opsForValue().set("code","1234",3, TimeUnit.MINUTES);//setex
	redisTemplate.opsForValue().setIfAbsent("lock","1");//setnx
}

操作哈希数据

public void testHash(){
	//hset hget hdel hkeys hvals
	HashOperations hashOperations = redisTemplate.opsForHash();
	hashOperations.put("100","name","tom");
	hashOperations.put("100","age","22");

	System.out.println((String) hashOperations.get("100","name"));
	Set keys = hashOperations.keys("100");
	System.out.println(keys);
	List values = hashOperations.values("100");
	System.out.println(values);
	hashOperations.delete("100","name");

}

操作列表数据

@Test
public void testList(){
	//lpush lrange rpop llen
	ListOperations listOperations = redisTemplate.opsForList();
	listOperations.leftPushAll("mylist","a","b","c");
	listOperations.leftPush("mylist","d");
	List mylist = listOperations.range("mylist", 0, -1);
	System.out.println(mylist);
	listOperations.rightPop("mylist");
	Long size = listOperations.size("mylist");
	System.out.println(size);
}

操作集合数据

public void testSet(){
	//sadd smember scard sinter sunion srem
	SetOperations setOperations = redisTemplate.opsForSet();
	setOperations.add("myset1","a","b","c");
	setOperations.add("myset2","b","c","d");
	setOperations.members("myset1");
	setOperations.size("myset1");
	setOperations.intersect("myset1","myset2");
	setOperations.union("myset1","myset2");
	setOperations.remove("myset1","a");
}

操作有序集合数据

public void testZset(){
	//zadd zrange zincrby zrem
	ZSetOperations zSetOperations = redisTemplate.opsForZSet();
	zSetOperations.add("zset1","a",10);
	zSetOperations.add("zset2","b",20);
	zSetOperations.add("zset3","c",30);
	zSetOperations.range("zset1",0,-1);
	zSetOperations.incrementScore("zset1","c",10);
	zSetOperations.remove("zset1","c");
}

通用操作命令

public void testCommon(){
	//keys exists type del
	Set keys =  redisTemplate.keys("*");
	redisTemplate.hasKey("name");
	for(Object key : keys){
		redisTemplate.type(key);
	}
	redisTemplate.delete("name");
}

店铺营业状态设置

代码很简单,主要是应用redis

HttpClient

可以用来提供高效的\最新的\功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新的版本和建议
maven坐标

<dependency>
	<groupId>org.apache.httpcomponents</groupId>
	<artifactId>httpclient</artifactId>
	<version>4.5.13</version>
</dependency>

发送GET方式请求

xxx.var是创建对象的快捷方式

/**
 * 通过httpclient发送GET请求
 */
@Test
public void testGet() throws IOException {
	//创建httpclient对象
	CloseableHttpClient httpClient = HttpClients.createDefault();

	//创建请求对象
	HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");

	//发送请求
	CloseableHttpResponse response = httpClient.execute(httpGet);
	//获取服务端返回的状态码
	int statusCode = response.getStatusLine().getStatusCode();
	System.out.println("服务器返回状态码"+statusCode);
	HttpEntity entity = response.getEntity();
	String body = EntityUtils.toString(entity);
	System.out.println("服务端返回的数据"+body);

	//关闭资源
	response.close();
	httpClient.close();
}

发送post方式请求

/**
 * post方式
 * @throws IOException
 */
@Test
public void testPOST() throws IOException {
	CloseableHttpClient httpClient = HttpClients.createDefault();
	HttpPost httpPost = new HttpPost("http://localhost:8080/admin/employee/login");
	JSONObject jsonObject = new JSONObject();
	jsonObject.put("username", "admin");
	jsonObject.put("password", "123456");
	StringEntity entity = new StringEntity(jsonObject.toString());
	entity.setContentEncoding("UTF-8");
	entity.setContentType("application/json");
	httpPost.setEntity(entity);

	CloseableHttpResponse response = httpClient.execute(httpPost);
	int statusCode = response.getStatusLine().getStatusCode();
	System.out.println("响应码为"+statusCode);
	HttpEntity entity1 = response.getEntity();
	String body = EntityUtils.toString(entity1);
	System.out.println("响应数据为"+body);

}

微信小程序

小程序ID wxa431188d22e68c6f
小程序密钥 7fb396a34c8de8b60626810145d85b89

小程序的目录结构

页面由四个文件组成,js(页面逻辑),wxml(页面结构),json(页面配置),wxss(页面样式)
logincode 0e1PyoGa1Xk6AI0UsOFa1Jwbcv1PyoGF

微信登录

@Configuration和@Bean联用,将第三方写好的类注入到spring框架中
@Component是注入自定义的类对象

缓存

为了用户能更高效地访问数据,需要使用redis将一些重要的反复需要访问的数据缓存
对服务器的压力也大
redis缓存只是将数据缓存在服务端中,客户端还是需要保持连接访问,或者使用其他技术将数据缓存在本地

菜品缓存

缓存

  • 每个分类下的菜品保存一份缓存数据
  • 数据库中菜品数据有变更时清理缓存数据
    就一个原则,怎么存进去的就怎么强转取出来就行了
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<DishVO>> list(Long categoryId) {
	//构造redis中的key,规则:dish_分类id
	String key = "dish_"+categoryId;
	//查询redis中是否存在菜品数据
	List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
	//如果存在,直接返回,无须查询数据库
	if(list !=null && list.size()>0){
		return Result.success(list);
	}

	//如果不存在,查询数据库,将查询到的数据放入redis中
	Dish dish = new Dish();
	dish.setCategoryId(categoryId);
	dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
	list = dishService.listWithFlavor(dish);
	redisTemplate.opsForValue().set(key,list);

	return Result.success(list);
}

清理缓存

当修改了被访问的数据,需要清理缓存,否则用户访问的是修改前的数据

  • 新增菜品
  • 修改菜品
  • 批量删除菜品
  • 起售\停售菜品
    这里实现的就是简单粗暴的都清理掉
/**
 * 清理缓存数据
 * @param pattern
 */
public void cleanCache(String pattern){
	Set keys = redisTemplate.keys(pattern);
	redisTemplate.delete(keys);
}

缓存套餐

spring cache框架,通过注解的方式缓存数据
maven坐标

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
  • @EnableCacheing,开启缓存注解功能,通常加载启动类上
  • Cacheable,方法执行前先查询是否有数据,有数据返回缓存数据,没有,调用犯法放入缓存,一般就是将这个函数的返回值放入或缓存和调出缓存
  • CachePut,将方法的返回值放到缓存中
  • CacheEvict,将一条或多条数据从缓存中删除
    因为是根据分类查询的菜品和套餐,所以这里缓存清除和缓存的逻辑还要好好琢磨一下,为什么是删除一个套餐时,清除的是整个分类,而不是单个套餐去在缓存中清理

添加购物车

在设计表与表时,可存在冗余字段,可提高查询速度,就不用查多个表了,省时间就要费空间

查看购物车

清空购物车

导入地址簿

  • 查询地址列表
  • 新增地址
  • 修改地址
  • 删除地址
  • 设置默认地址
  • 查询默认地址(在下单时,需要展示)

用户下单

订单支付

需要企业认证,个人无法认证微信支付

  • 付款码支付
  • JSAPI支付
  • 小程序支付
  • Native支付
  • APP支付
  • 刷脸支付
    这里用传智教育的商户号

微信支付准备工作

  • 准备证书文件
  • 为当前电脑生成临时ip
    ./cpolar authtoken MzY1ZGI1OTAtNTgzYy00MjdmLWFlYTItYTBjMTQ1ZjI0NWNm//开启cpolar服务
    $ ./cpolar http 80//关联端口号
    cpolar,通过内网穿透,临时获取一个内网域名,可以访问当前电脑上的资源,因为我连接了互联网,所以在我的手机上也能访问电脑资源

订单相关模块

用户端订单管理

查询历史订单

查询订单详情

取消订单

再来一单

商家端订单管理模块

订单搜索

各状态订单数量统计

查询订单详情

接单

拒单

取消订单

派送订单

完成订单

校验收货地址是否超出配送范围

AK:GaFQYZYzEJW6ky7sm62xBxHxpBNRyc1D
address:辽宁省沈阳市和平区文化路3号巷11号东北大学(南湖校区)

定时处理

Spring Task

提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑
比如,信用卡每月还款提醒\火车票售票系统处理未支付订单
cron表达式,规则不用记,预期的定时可以用在线的cron生成器生成
不用导入maven坐标,springboot集成了

  • 启动类添加注解,@EnableScheduling //开启任务调度
  • 自定义定时任务类

订单定时状态处理

  • 下单后未支付,订单一直处于待支付状态
    解决方案,通过定时任务每分钟检查一次是否存在支付超时订单,存在就取消
    相比于针对每个用户的定时开销小太多了,也没必要定时1s就查一次,这对服务器的压力也很大
  • 用户收货后管理端未点击完成按钮,订单一直处于派送中状态
    解决方案,每天凌晨(或者打烊后)检查一次是否存在派送中订单

WebSocket

是基于TCP的一种新的网络协议,实现了浏览器与服务器全双工通信-浏览器与服务器只需要一次握手,就可创建持久性的连接,并双向数据传输
HTTP协议只能先由客户端发出请求再由服务端响应

  • 短链接
  • 单向
    而webSocket在握手后就可以双向传输,很流氓,我客户端没要,你凭什么提供给我,但确实使客户端傻瓜操作
  • 长连接
  • 双向
  • 应用,视频弹幕\网页聊天\股市体育实时更新
    maven坐标
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

但底层都基于TCP协议
*Configuration.java

/**
 * WebSocket配置类,用于注册WebSocket的Bean
 */
@Configuration
public class WebSocketConfiguration {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

*websocketserver.java

/**
 * WebSocket服务
 */
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {

    //存放会话对象
    private static Map<String, Session> sessionMap = new HashMap();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        System.out.println("客户端:" + sid + "建立连接");
        sessionMap.put(sid, session);
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, @PathParam("sid") String sid) {
        System.out.println("收到来自客户端:" + sid + "的信息:" + message);
    }

    /**
     * 连接关闭调用的方法
     *
     * @param sid
     */
    @OnClose
    public void onClose(@PathParam("sid") String sid) {
        System.out.println("连接断开:" + sid);
        sessionMap.remove(sid);
    }

    /**
     * 群发
     *
     * @param message
     */
    public void sendToAllClient(String message) {
        Collection<Session> sessions = sessionMap.values();
        for (Session session : sessions) {
            try {
                //服务器向客户端发送消息
                session.getBasicRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

可以使用定时任务周期骚扰

@Component
public class WebSocketTask {
    @Autowired
    private WebSocketServer webSocketServer;

    /**
     * 通过WebSocket每隔5秒向客户端发送消息
     */
    @Scheduled(cron = "0/5 * * * * ?")
    public void sendMessageToClient() {
        webSocketServer.sendToAllClient("这是来自服务端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
    }
}

来单提醒及客户催单

springTask和webSocket的联用
和HTTP协议的接口文档规定一样,websocket同样需要前后端的约定
约定通讯的数据格式为json,type字段为数据类型

来单提醒

实现不了,需要在实现支付后微信官方回调我本地服务器的Controller

客户催单

统计功能模块

Apache ECharts

基于javascript的数据可视化图表库(前端技术)
作为后端程序员,使用echarts,重点在于研究当前图表所需的数据格式.

营业额统计

用户统计

订单统计

销量统计

工作台模块

导出运营数据excel报表

Apache POI

处理miscrosoft Office各种文件的开源项目,可以使用POI在java程序中对office各种文件进行读写操作
maven坐标

<!-- poi -->
<dependency>
	<groupId>org.apache.poi</groupId>
	<artifactId>poi</artifactId>
</dependency>
<dependency>
	<groupId>org.apache.poi</groupId>
	<artifactId>poi-ooxml</artifactId>
</dependency>

导出运营数据excel报表

添加"运营数据报表模板.xlsx"模板文件到后端resources/template中


网站公告

今日签到

点亮在社区的每一天
去签到

热门文章