企业可以做哪些网站,亿度网络 网站建设,网页设计与网站建设分析,商洛市城乡建设规划局网站前言
1、软件开发流程
瀑布模型需求分析//需求规格说明书、产品原型↓ 设计 //UI设计、数据库设计、接口设计↓编码 //项目代码、单元测试↓ 测试 //测试用例、测试报告↓上线运维 //软件环境安装、配置第一阶段#xff1a;需求分析需求规格说明书、产品原型一般来说…前言
1、软件开发流程
瀑布模型需求分析//需求规格说明书、产品原型↓ 设计 //UI设计、数据库设计、接口设计↓编码 //项目代码、单元测试↓ 测试 //测试用例、测试报告↓上线运维 //软件环境安装、配置第一阶段需求分析需求规格说明书、产品原型一般来说就是使用 **Word** 文档来描述当前项目的各个组成部分如系统定义、应用环境、功能规格、性能需求等都会在文档中描述产品原型一般是使用**Axure RP**软件设计。第二阶段: 设计设计的内容包含 UI设计、数据库设计、接口设计。UI设计用户界面的设计主要设计项目的页面效果小到一个按钮大到一个页面布局还有人机交互逻辑的体现。数据库设计使用**PowerDesigner**设计当前项目中涉及到哪些数据库每一个数据库里面包含哪些表这些表结构之间的关系是什么样的表结构中包含哪些字段。接口设计通过分析原型图首先粗粒度地分析每个页面有多少接口然后再细粒度地分析每个接口的传入参数返回值参数同时明确接口路径及请求方式。第三阶段: 编码编写项目代码、并完成单元测试接口测试。项目代码编写作为软件开发工程师我们需要对项目的模块功能分析后进行编码实现。单元测试编码实现完毕后进行单元测试单元测试通过后再进入到下一阶段第四阶段: 测试在该阶段中主要由测试人员, 对部署在测试环境的项目进行功能测试, 并出具测试报告。第五阶段: 上线运维在项目上线之前 会由运维人员准备服务器上的软件环境安装、配置 配置完毕后 再将我们开发好的项目部署在服务器上运行。2、角色分工
岗位/角色对应阶段职责/分工项目经理PMproject manager全阶段对整个项目负责任务分配、把控进度产品经理需求分析进行需求调研输出需求调研文档、产品原型等UI设计师设计根据产品原型输出界面效果图架构师资深工程师设计项目整体架构设计、技术选型等开发工程师编码功能代码实现测试工程师测试编写测试用例输出测试报告运维工程师上线运维软件环境搭建、项目上线
3、软件环境
1). 开发环境dev(development)
我们作为软件开发人员在开发阶段使用的环境就是开发环境一般外部用户无法访问。2). 测试环境test(testing)
当软件开发工程师将项目的功能模块开发完毕并且单元测试通过后就需要将项目部署到测试服务器上让测试人员对项目进行测试。那这台测试服务器就是专门给测试人员使用的环境 也就是测试环境用于项目测试一般外部用户无法访问。3). 生产环境prod(production) 当项目开发完毕并且由测试人员测试通过之后就可以上线项目将项目部署到线上环境并正式对外提供服务这个线上环境也称之为生产环境。首先会在开发环境中进行项目开发往往开发环境大多数都是本地的电脑环境和局域网内的环境当开发完毕后然后会把项目部署到测试环境测试环境一般是一台独立测试服务器的环境项目测试通过后最终把项目部署到生产环境生产环境可以是机房或者云服务器等线上环境。一、苍穹外卖
1.项目介绍 苍穹外卖 { 管理端 { 员工管理 分类管理 菜品管理 套餐管理 订单管理 工作台 数据统计 来电提醒 用户端 { 微信登录 商品浏览 购物车 用户下单 微信支付 历史订单 地址管理 用户催单 苍穹外卖 \begin{cases} 管理端 \begin{cases}员工管理\\ 分类管理\\ 菜品管理\\ 套餐管理\\ 订单管理\\ 工作台\\ 数据统计\\ 来电提醒 \end{cases}\\ \\ 用户端 \begin{cases}微信登录\\ 商品浏览\\ 购物车\\ 用户下单\\ 微信支付\\ 历史订单\\ 地址管理\\ 用户催单 \end{cases} \end{cases} 苍穹外卖⎩ ⎨ ⎧管理端用户端⎩ ⎨ ⎧员工管理分类管理菜品管理套餐管理订单管理工作台数据统计来电提醒⎩ ⎨ ⎧微信登录商品浏览购物车用户下单微信支付历史订单地址管理用户催单
1). 管理端
餐饮企业内部员工使用。 主要功能有:
模块描述登录/退出内部员工必须登录后,才可以访问系统管理后台员工管理管理员可以在系统后台对员工信息进行管理包含查询、新增、编辑、禁用等功能分类管理主要对当前餐厅经营的 菜品分类 或 套餐分类 进行管理维护 包含查询、新增、修改、删除等功能菜品管理主要维护各个分类下的菜品信息包含查询、新增、修改、删除、启售、停售等功能套餐管理主要维护当前餐厅中的套餐信息包含查询、新增、修改、删除、启售、停售等功能订单管理主要维护用户在移动端下的订单信息包含查询、取消、派送、完成以及订单报表下载等功能数据统计主要完成对餐厅的各类数据统计如营业额、用户数量、订单等
2). 用户端
移动端应用主要提供给消费者使用。主要功能有:
模块描述登录/退出用户需要通过微信授权后登录使用小程序进行点餐点餐-菜单在点餐界面需要展示出菜品分类/套餐分类, 并根据当前选择的分类加载其中的菜品信息, 供用户查询选择点餐-购物车用户选中的菜品就会加入用户的购物车, 主要包含 查询购物车、加入购物车、删除购物车、清空购物车等功能订单支付用户选完菜品/套餐后, 可以对购物车菜品进行结算支付, 这时就需要进行订单的支付个人信息在个人中心页面中会展示当前用户的基本信息, 用户可以管理收货地址, 也可以查询历史订单数据
2、技术选型 关于本项目的技术选型, 我们将会从 用户层、网关层、应用层、数据层 这几个方面进行介绍主要用于展示项目中使用到的技术框架和中间件等。#mermaid-svg-E7o7CUjX2KDOSBv3 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-E7o7CUjX2KDOSBv3 .error-icon{fill:#552222;}#mermaid-svg-E7o7CUjX2KDOSBv3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-E7o7CUjX2KDOSBv3 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-E7o7CUjX2KDOSBv3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-E7o7CUjX2KDOSBv3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-E7o7CUjX2KDOSBv3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-E7o7CUjX2KDOSBv3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-E7o7CUjX2KDOSBv3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-E7o7CUjX2KDOSBv3 .marker.cross{stroke:#333333;}#mermaid-svg-E7o7CUjX2KDOSBv3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-E7o7CUjX2KDOSBv3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-E7o7CUjX2KDOSBv3 .cluster-label text{fill:#333;}#mermaid-svg-E7o7CUjX2KDOSBv3 .cluster-label span{color:#333;}#mermaid-svg-E7o7CUjX2KDOSBv3 .label text,#mermaid-svg-E7o7CUjX2KDOSBv3 span{fill:#333;color:#333;}#mermaid-svg-E7o7CUjX2KDOSBv3 .node rect,#mermaid-svg-E7o7CUjX2KDOSBv3 .node circle,#mermaid-svg-E7o7CUjX2KDOSBv3 .node ellipse,#mermaid-svg-E7o7CUjX2KDOSBv3 .node polygon,#mermaid-svg-E7o7CUjX2KDOSBv3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-E7o7CUjX2KDOSBv3 .node .label{text-align:center;}#mermaid-svg-E7o7CUjX2KDOSBv3 .node.clickable{cursor:pointer;}#mermaid-svg-E7o7CUjX2KDOSBv3 .arrowheadPath{fill:#333333;}#mermaid-svg-E7o7CUjX2KDOSBv3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-E7o7CUjX2KDOSBv3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-E7o7CUjX2KDOSBv3 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-E7o7CUjX2KDOSBv3 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-E7o7CUjX2KDOSBv3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-E7o7CUjX2KDOSBv3 .cluster text{fill:#333;}#mermaid-svg-E7o7CUjX2KDOSBv3 .cluster span{color:#333;}#mermaid-svg-E7o7CUjX2KDOSBv3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-E7o7CUjX2KDOSBv3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 苍穹外卖 用户层 网管层 node.js VUE.js ElementUI 微信小程序 apache echarts Nginx #mermaid-svg-FIzGgpGoRdtogPzM {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-FIzGgpGoRdtogPzM .error-icon{fill:#552222;}#mermaid-svg-FIzGgpGoRdtogPzM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FIzGgpGoRdtogPzM .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-FIzGgpGoRdtogPzM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FIzGgpGoRdtogPzM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FIzGgpGoRdtogPzM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FIzGgpGoRdtogPzM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FIzGgpGoRdtogPzM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FIzGgpGoRdtogPzM .marker.cross{stroke:#333333;}#mermaid-svg-FIzGgpGoRdtogPzM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FIzGgpGoRdtogPzM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FIzGgpGoRdtogPzM .cluster-label text{fill:#333;}#mermaid-svg-FIzGgpGoRdtogPzM .cluster-label span{color:#333;}#mermaid-svg-FIzGgpGoRdtogPzM .label text,#mermaid-svg-FIzGgpGoRdtogPzM span{fill:#333;color:#333;}#mermaid-svg-FIzGgpGoRdtogPzM .node rect,#mermaid-svg-FIzGgpGoRdtogPzM .node circle,#mermaid-svg-FIzGgpGoRdtogPzM .node ellipse,#mermaid-svg-FIzGgpGoRdtogPzM .node polygon,#mermaid-svg-FIzGgpGoRdtogPzM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FIzGgpGoRdtogPzM .node .label{text-align:center;}#mermaid-svg-FIzGgpGoRdtogPzM .node.clickable{cursor:pointer;}#mermaid-svg-FIzGgpGoRdtogPzM .arrowheadPath{fill:#333333;}#mermaid-svg-FIzGgpGoRdtogPzM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FIzGgpGoRdtogPzM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FIzGgpGoRdtogPzM .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-FIzGgpGoRdtogPzM .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-FIzGgpGoRdtogPzM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FIzGgpGoRdtogPzM .cluster text{fill:#333;}#mermaid-svg-FIzGgpGoRdtogPzM .cluster span{color:#333;}#mermaid-svg-FIzGgpGoRdtogPzM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FIzGgpGoRdtogPzM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 苍穹外卖 应用层 数据层 Spring Boot Spring MVC Spring Task httpclient Spring Cache JWT 阿里云OSS Swagger POI WebSocket MySQL Redis mybatis pagehelper spring Data rdis 1). 用户层前端技术 本项目中在构建系统管理后台的前端页面我们会用到H5、Vue.js、ElementUI、apache echarts(展示图表)等技术。而在构建移动端应用时我们会使用到微信小程序。2). 网关层服务器 Nginx是一个服务器主要用来作为Http服务器部署静态资源访问性能高。在Nginx中还有两个比较重要的作用 反向代理和负载均衡 在进行项目部署时要实现Tomcat的负载均衡就可以通过Nginx来实现3).应用层
SpringBoot 快速构建Spring项目, 采用 约定优于配置 的思想, 简化Spring项目的配置开发。SpringMVCSpringMVC是spring框架的一个模块springmvc和spring无需通过中间整合层进行整合可以无缝集成。Spring Task: 由Spring提供的定时任务框架。httpclient: 主要实现了对http请求的发送。Spring Cache: 由Spring提供的数据缓存框架JWT: 用于对应用程序上的用户进行身份验证的标记。阿里云OSS: 对象存储服务在项目中主要存储文件如图片等。Swagger 可以自动的帮助开发人员生成接口文档并对接口进行测试。POI: 封装了对Excel表格的常用操作。WebSocket: 一种通信网络协议使客户端和服务器之间的数据交换更加简单用于项目的来单、催单功能实现。4). 数据层
MySQL 关系型数据库, 本项目的核心业务数据都会采用MySQL进行存储。Redis 基于key-value格式存储的内存数据库, 访问速度快, 经常使用它做缓存。Mybatis 本项目持久层将会使用Mybatis开发。pagehelper: 分页插件。spring data redis: 简化java代码操作Redis的API。5). 工具
git: 版本控制工具, 在团队协作中, 使用该工具对项目中的代码进行管理。maven: 项目构建工具。junit单元测试工具开发人员功能实现完毕后需要通过junit对功能进行单元测试。postman: 接口测工具模拟用户发起的各类HTTP请求获取对应的响应结果。3、开发环境搭建 { 前端 { 管理端 W e b 用户端 ( 小程序 ) 后端 后端服务 ( J A V A ) \begin{cases}前端 \begin{cases}管理端Web\\用户端(小程序) \end{cases} \\\\ 后端 后端服务(JAVA) \end{cases} ⎩ ⎨ ⎧前端后端{管理端Web用户端(小程序)后端服务(JAVA)
开发环境搭建主要包含前端环境和后端环境两部分。
1) .前端环境搭建 前端工程基于 nginx,为了避免之前启动的nginx占用了80端口可关掉之前启动的其他nginx2).后端环境搭建
分析sky-common模块的每个包的作用
名称说明constant存放相关常量类context存放上下文类enumeration项目的枚举类存储exception存放自定义异常类json处理json转换的类properties存放SpringBoot相关的配置属性类result返回结果类的封装utils常用工具类
分析sky-server模块的每个包的作用
名称说明config存放配置类controller存放controller类interceptor存放拦截器类mapper存放mapper接口service存放service类SkyApplication启动类
3Git版本控制 使用Git进行项目代码的版本控制创建Git远程访问https://gitee.com/projects/new新建仓库sky-take-out3). 将本地文件推送到Git远程仓库提交文件至本地仓库添加Git远程仓库地址进行推送成功推送至远程仓库。4数据库环境搭建
在数据库中创建如下表格
序号表名中文名1employee员工表2category分类表3dish菜品表4dish_flavor菜品口味表5setmeal套餐表6setmeal_dish套餐菜品关系表7user用户表8address_book地址表9shopping_cart购物车表10orders订单表11order_detail订单明细表 建表规范(阿里开源的Java开发手册(黄山版).pdf)
- 【强制】表名、字段名必须使用小写字母或数字禁止出现数字开头禁止两个下划线中间只出现数字。数据库字段名的修改代价很大因为无法进行预发布所以字段名称需要慎重考虑。说明MySQL 在 Windows 下不区分大小写但在 Linux 下默认是区分大小写。因此数据库名、表名、字段名都不允许出现任何大写字母避免节外生枝。正例aliyun_adminrdc_configlevel3_name反例AliyunAdminrdcConfiglevel_3_name
- 【强制】表名不使用复数名词。说明表名应该仅仅表示表里面的实体内容不应该表示实体数量对应于 DO 类名也是单数形式符合表达习惯。
- 【强制】禁用保留字如 order, by, desc、range等请参考 MySQL 官方保留字。5).前后端联调
1准备工作- 确认MySQL密码打开application.yml会看到配置如下
spring:datasource:druid:driver-class-name: ${sky.datasource.driver-class-name}username: ${sky.datasource.username}password: ${sky.datasource.password}
发现使用的是${...}作用其实和注解Value(${user.age})一样都是从配置文件中读取配置.#启动之前记得确认数据库配置application-dev.yml
sky:datasource:driver-class-name: com.mysql.cj.jdbc.Driverhost: localhostport: 3306database: sky_take_outusername: root#改成自己的数据库密码password: root
编译父工程sky-take-out保证依赖的所有jar包都下载成功启动SkyApplication直接进行前后端联调测试即可http://localhostTomcat started on port(s): 8080 (http) with context path
com.sky.SkyApplication: Started SkyApplication in 4.585 seconds (JVM running for 5.475)
com.sky.SkyApplication: server started# 3后端的初始工程中已经实现了**登录**功能通过debug查看执行流程4、JWT令牌技术
JWT全称JSON Web Token 官网https://jwt.io/- 定义了一种简洁的、自包含的格式用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在这些信息是可靠的。简洁是指jwt就是一个简单的字符串。可以在请求参数或者是请求头当中直接传递。自包含指的是jwt令牌看似是一个随机的字符串但是我们是可以根据自身的需求在jwt令牌中存储自定义的数据内容。如可以直接在jwt令牌中存储用户的相关信息。简单来讲jwt就是将原始的json数据格式进行了安全的封装这样就可以直接基于jwt在通信双方安全的进行信息传输了。- 第一部分Header(头 记录令牌类型、签名算法等。 例如{alg:HS256,type:JWT}- 第二部分Payload(有效载荷携带一些自定义信息、默认信息等。 例如{id:1,username:Tom}- 第三部分Signature(签名防止Token被篡改确保安全性。将header、payload并加入指定秘钥通过指定签名算法计算而来HS256headerpayload, secretBase64是一种基于64个可打印的字符来表示二进制数据的编码方式。既然能编码那也就意味着也能解码。所使用
的64个字符分别是A到Z、a到z、 0- 9一个加号一个斜杠加起来就是64个字符。任何数据经过base64编码之后
最终就会通过这64个字符来表示。当然还有一个符号那就是等号。等号它是一个补位的符号.需要注意的是Base64是编码方式而不是加密方式。JWT令牌最典型的应用场景就是登录认证1. 在浏览器发起请求来执行登录操作此时会访问登录的接口如果登录成功之后我们需要生成一个jwt令牌将生成的 jwt令牌返回给前端。
2. 前端拿到jwt令牌之后会将jwt令牌存储起来。在后续的每一次请求中都会将jwt令牌携带到服务端。
3. 服务端统一拦截请求之后先来判断一下这次请求有没有把令牌带过来如果没有带过来直接拒绝访问如果带过
来了还要校验一下令牌是否是有效。如果有效就直接放行进行请求的处理。在JWT登录认证的场景中我们发现整个流程当中涉及到两步操作1. 在登录成功之后要生成令牌。
2. 每一次请求当中要接收令牌并对令牌进行校验。5、Nginx反向代理和负载均衡
1、nginx反向代理
前端发送的请求是如何请求到后端服务的很明显前端请求和后端接口两个地址不一致那是如何请求到后端服务的呢**nginx 反向代理**就是将前端发送的**动态请求**由 nginx 转发到后端服务器。**nginx 反向代理的好处**
- 提高访问速度因为nginx本身可以进行缓存如果访问的同一接口并且做了数据缓存nginx就直接可把数据返回不需要真正地访问服务端从而提高访问速度。- 进行负载均衡所谓负载均衡,就是把大量的请求按照我们指定的方式均衡的分配给集群中的每台服务器。- 保证后端服务安全因为一般后台服务地址不会暴露所以使用浏览器不能直接访问可以把nginx作为请求访问的入口请求到达nginx后转发到具体的服务中从而保证后端服务的安全。
nginx 反向代理的配置方式 server{listen 80; #监听80端口号server_name localhost;# 反向代理,处理管理端发送的请求location /api/{proxy_pass http://localhost:8080/admin/; #反向代理}
}
# **proxy_pass**该指令是用来设置代理服务器的地址可以是主机名称IP地址加端口号等形式。打开nginx配置: nginx.conf当在访问http://localhost/api/employee/loginnginx接收到请求后转到http://localhost:8080/admin/故最终的请求地址为http://localhost:8080/admin/employee/login和后台服务的访问地址一致。2、负载均衡 当如果服务以集群的方式进行部署时那nginx在转发请求到服务器时就需要做相应的负载均衡。其实负载均衡从本质上来说也是基于反向代理来实现的最终都是转发请求。#nginx 负载均衡的配置方式
upstream webservers{ #**upstream**如果代理服务器是一组服务器的话我们可以使用upstream指令配置后端服务器组。server 192.168.100.128:8080;server 192.168.100.129:8080;
}
server{listen 80; #监听80端口号server_name localhost;location /api/{proxy_pass http://webservers/admin;#负载均衡}
}
#upstream后面的名称可自定义但要上下保持一致。**nginx 负载均衡策略**名称说明轮询默认方式weight权重方式默认为1权重越高被分配的客户端请求就越多ip_hash依据ip分配方式这样每个访客可以固定访问一个后端服务least_conn依据最少连接方式把请求优先分配给连接数少的后端服务url_hash依据url分配方式这样相同的url会被分配到同一个后端服务fair依据响应时间方式响应时间短的服务将会被优先分配
**轮询【默认】【常用】**
upstream webservers{server 192.168.100.128:8080;server 192.168.100.129:8080;
}weight:权重越高被分配的客户端请求就越多
upstream webservers{server 192.168.100.128:8080 weight90;server 192.168.100.129:8080 weight10;
}**ip_hash:依据ip分配方式这样每个访客可以固定访问一个后端服务**
upstream webservers{ip_hash;server 192.168.100.128:8080;server 192.168.100.129:8080;
}**least_conn:** 依据最少连接方式把请求优先分配给连接数少的后端服务
upstream webservers{least_conn;server 192.168.100.128:8080;server 192.168.100.129:8080;
}**url_hash:**依据url分配方式这样相同的url会被分配到同一个后端服务
#url http://localhost:8088/admin/employee?id10
#uri admin/employee?id10
upstream webservers{hash $request_uri;server 192.168.100.128:8080;server 192.168.100.129:8080;
}
**fair:**依据响应时间方式响应时间短的服务将会被优先分配
upstream webservers{server 192.168.100.128:8080;server 192.168.100.129:8080;fair;
}3). 总结
Nginx的主要作用1. 部署静态资源
2. 反向代理
3. 负载均衡6、MD5加密登录
修改数据库中明文密码改为MD5加密后的密文
String pwd DigestUtils.md5DigestAsHex(123456.getBytes());
/#/ e10adc3949ba59abbe56e057f20f883e
7、导入接口文档
KaTeX parse error: Expected EOF, got at position 86: …ses} \right\}连调̲ 提测
第一步定义接口确定接口的路径、请求方式、传入参数、返回参数。第二步前端开发人员和后端开发人员并行开发同时也可自测。第三步前后端人员进行连调测试。第四步提交给测试人员进行最终测试。操作步骤1). 从资料中找到项目接口文件2). 导入到YApi平台 在YApi平台创建出两个项目选择苍穹外卖-管理端接口.json导入8、Swagger
Swagger 是一个规范和完整的框架用于生成、描述、调用和可视化 RESTful 风格的 Web 服务(https://swagger.io/)。 它的主要作用是1. 使得前后端分离开发更加方便有利于团队协作2. 接口的文档在线自动生成降低后端开发人员编写接口文档的负担3. 功能测试 Spring已经将Swagger纳入自身的标准建立了Spring-swagger项目现在叫Springfox。通过在项目中引入Springfox 即可非常简单快捷的使用Swagger。knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍!目前一般都使用knife4j框架。 使用步骤
1、导入 knife4j 的maven坐标
在pom.xml中添加依赖
dependencygroupIdcom.github.xiaoymin/groupIdartifactIdknife4j-spring-boot-starter/artifactId
/dependency
2、在配置类中加入 knife4j 相关配置WebMvcConfiguration.java/*** 通过knife4j生成接口文档* return
*/
Bean
public Docket docket() {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;
}3、设置静态资源映射否则接口文档页面无法访问WebMvcConfiguration.java
/*** 设置静态资源映射* param registry
*/
protected void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler(/doc.html)//knife4j-spring-ui-3.0.2.jar包里边.addResourceLocations(classpath:/META-INF/resources/);registry.addResourceHandler(/webjars/**).addResourceLocations(classpath:/META-INF/resources/webjars/);
}常用的注解
注解说明Api用在类上例如Controller表示对类的说明ApiModel用在类上例如entity、DTO、VOApiModelProperty用在属性上描述属性信息ApiOperation用在方法上例如Controller的方法说明方法的用途、作用
Api(tags 员工相关接口)PostMapping(/logout)ApiOperation(员工退出)ApiModelProperty(用户名)
public class EmployeeLoginDTO implements Serializable {}
ApiModel(description 员工登录返回的数据格式)public class EmployeeLoginVO implements Serializable {ApiModelProperty(主键值)private Long id;ApiModelProperty(用户名)private String userName;ApiModelProperty(姓名)private String name;ApiModelProperty(jwt令牌)private String token;
}二、苍穹外卖2
1、ThreadLocal
ThreadLocal 并不是一个Thread而是Thread的局部变量。 ThreadLocal为每个线程提供单独一份存储空间具有线程隔离的效果只有在线程内才能获取到对应的值线程外则不能访问。**常用方法**
- public void set(T value) 设置当前线程的线程局部变量的值
- public T get() 返回当前线程所对应的线程局部变量的值
- public void remove() 移除当前线程的线程局部变量通过代码验证当tomcat接受到请求后拦截器 - Controller - Service - Mapper是不是同一个线程//通过线程ID来验证
System.out.println(当前线程ID Thread.currentThread().getId());实现记录操作人
初始工程在sky-common模块中已经封装了 ThreadLocal 操作的工具类package com.sky.context;public class BaseContext {public static ThreadLocalLong threadLocal new ThreadLocal();public static void setCurrentId(Long id) {threadLocal.set(id);}public static Long getCurrentId() {return threadLocal.get();}public static void removeCurrentId() {threadLocal.remove();}}
● 在拦截器中解析出当前登录员工id并放入线程局部变量中
package com.sky.interceptor;/*** jwt令牌校验的拦截器*/
Component
Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {Autowiredprivate JwtProperties jwtProperties;/*** 校验jwt** param request* param response* param handler* return* throws Exception*/public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//.............................//2、校验令牌try {//.................Claims claims JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);Long empId Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());log.info(当前员工id{}, empId);/将用户id存储到ThreadLocalBaseContext.setCurrentId(empId);//3、通过放行return true;} catch (Exception ex) {//......................}}Override//请求结束之前执行清理ThreadLocal存储的IDpublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {BaseContext.removeCurrentId();}
}2、pageQuery分页插件 mybatis 的分页插件 PageHelper 来简化分页代码的开发。底层基于 mybatis 的拦截器实现。在pom.xml文中添加依赖
dependencygroupIdcom.github.pagehelper/groupIdartifactIdpagehelper-spring-boot-starter/artifactId
/dependency
/*** 分页查询** param employeePageQueryDTO* return*/
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {// select * from employee limit 0,10//开始分页查询PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());PageEmployee page employeeMapper.pageQuery(employeePageQueryDTO);//后续定义long total page.getTotal();ListEmployee records page.getResult();return new PageResult(total, records);
}//在 src/main/resources/mapper/EmployeeMapper.xml 中编写SQL1、xml放置位置可以在配置文件中自行指定mybatis:mapper-locations: classpath:mapper/*.xml #xml文件位置type-aliases-package: com.sky.entity #实体类所在包2、方法返回类型为PageEmployee使用resultType指定时写Page中泛型的类型Employee即可
3、com.sky.entity.Employee包名类名比较繁琐可以进行简化在配置中指定包名!--完整包名类名--
!-- select idpageQuery resultTypecom.sky.entity.Employee --
!--在配置文件中指定包名后只写类名即可Employee, employee都可以--
select idpageQuery resultTypeEmployeeselect * from employeewhereif testname ! null and name ! and name like concat(%,#{name},%)/if/whereorder by create_time desc
/select3、完善时间显示
方式一在Employee类的属性上加上注解对日期进行格式化。
JsonFormat(pattern yyyy-MM-dd HH:mm:ss)
private LocalDateTime createTime;JsonFormat(pattern yyyy-MM-dd HH:mm:ss)
private LocalDateTime updateTime;
但这种方式需要在每个时间属性上都要加上该注解使用较麻烦不能全局处理。方式二(推荐)在WebMvcConfiguration中扩展SpringMVC的消息转换器使用自定义的JacksonObjectMapper统一对日期类型进行格式处理其中JacksonObjectMapper是通用的类已经定义好直接使用即可。package com.sky.json;/*** 对象映射器:基于jackson将Java对象转为json或者将json转为Java对象* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]*/
public class JacksonObjectMapper extends ObjectMapper {public static final String DEFAULT_DATE_FORMAT yyyy-MM-dd;//public static final String DEFAULT_DATE_TIME_FORMAT yyyy-MM-dd HH:mm:ss;public static final String DEFAULT_DATE_TIME_FORMAT yyyy-MM-dd HH:mm;public static final String DEFAULT_TIME_FORMAT HH:mm:ss;//省略后续代码
}
/*******************************************************************************************************************************//*** 扩展Spring MVC框架的消息转化器* param converters*/
protected void extendMessageConverters(ListHttpMessageConverter? converters) {log.info(扩展消息转换器...);//创建一个消息转换器对象注意不要导错包是Jackson2HttpMappingJackson2HttpMessageConverter converter new MappingJackson2HttpMessageConverter();//需要为消息转换器设置一个对象转换器对象转换器可以将Java对象序列化为json数据converter.setObjectMapper(new JacksonObjectMapper());//将自己的消息转化器加入容器中converters.add(0, converter);}4、AOP切面编程
我们使用AOP切面编程实现功能增强来完成公共字段自动填充功能。
AOP(面向切面编程)
重要名词通知Advice方法中的共性功能切入点Pointcut哪些方法, 切面Aspect描述切入点和通知位置关系通知类型前置后置方法前边加还是后边加
在插入或者更新的时候为指定字段赋予指定的值使用它的好处就是可以统一对这些字段进行处理避免了重复代码。
★ 技术点枚举、注解、AOP、反射.
1、自定义注解/*** 自定义注解用于标识某个方法需要进行功能字段自动填充处理*/
Documented //注解是否将包含在JavaDoc中
Target(ElementType.METHOD) //指定注解可以加在什么地方(类方法成员变量)
Retention(RetentionPolicy.RUNTIME) //定义注解的生命周期
public interface AutoFill {//数据库操作类型UPDATE INSERTOperationType value() default OperationType.INSERT;//使用value时候AutoFill(value OperationType.UPDATE)
}其中RetentionPolicy的不同策略对应的生命周期如下- RetentionPolicy.SOURCE : 仅存在于源代码中编译阶段会被丢弃不会包含于class字节码文件中。Override, SuppressWarnings都属于这类注解。- RetentionPolicy.CLASS : 默认策略在class字节码文件中存在在类加载时被丢弃运行时无法获取到- RetentionPolicy.RUNTIME : 始终不会丢弃可以使用反射获得该注解的信息。自定义的注解最常用的使用方式。2、枚举定义package com.sky.enumeration;/*** 数据库操作类型*/
public enum OperationType {/*** 更新操作*/UPDATE,/*** 插入操作*/INSERT
}3、切入点表达式要进行增强的方法的描述方式
//1.execution([访问修饰符] 返回值 包名.类/接口名.方法名(参数) [异常名])
execution(public User com.itheima.service.UserService.findById(int))
//2.当方法上使用指定注解时
annotation(com.sky.annotation.AutoFill)4、自定义切面类/*** 前置通知在通知中进行公共字段的赋值*/
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();//根据当前不同的操作类型为对应的属性通过反射来赋值if (operationType OperationType.INSERT) {//为4个公共字段赋值try {//通过方法名和参数类型获取定义好的方法setCreateTimeMethod 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) {//为2个公共字段赋值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();}}
} 5、加入AutoFill注解
**在Mapper接口的方法上加入 AutoFill 注解**Mapper
public interface CategoryMapper {/*** 插入数据* param category*/Insert(insert into category(type, name, sort, status, create_time, update_time, create_user, update_user) VALUES (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}))AutoFill(value OperationType.INSERT)void insert(Category category);/*** 根据id修改分类* param category*/AutoFill(value OperationType.UPDATE)void update(Category category);
}
**同时**将业务层为公共字段赋值的代码注释掉。
1). 将员工管理的**新增**和**编辑**方法中的公共字段赋值的代码注释。
//employee.setCreateTime(LocalDateTime.now());
//employee.setUpdateTime(LocalDateTime.now());
//employee.setCreateUser(BaseContext.getCurrentId());
//employee.setUpdateUser(BaseContext.getCurrentId());2). 将菜品分类管理的新增和修改方法中的公共字段赋值的代码注释。
//设置创建时间、修改时间、创建人、修改人
//category.setCreateTime(LocalDateTime.now());
//category.setUpdateTime(LocalDateTime.now());
//category.setCreateUser(BaseContext.getCurrentId());
//category.setUpdateUser(BaseContext.getCurrentId());5、阿里云OSS存储
1). 定义OSS相关配置# application-dev.yml
#sky:alioss:endpoint: oss-cn-hangzhou.aliyuncs.comaccess-key-id: LTAI5tPeFLzsPPT8gG3LPW64access-key-secret: U6k1brOZ8gaOIXv3nXbulGTUzy6Pd7#改成自己创建桶的名称bucket-name: sky-take-out#application.yml
spring:profiles:active: dev #设置环境
sky:alioss:endpoint: ${sky.alioss.endpoint}access-key-id: ${sky.alioss.access-key-id}access-key-secret: ${sky.alioss.access-key-secret}bucket-name: ${sky.alioss.bucket-name}2). 读取OSS配置【已定义】在sky-common模块中package com.sky.properties;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;Component
ConfigurationProperties(prefix sky.alioss)
Data
public class AliOssProperties {private String endpoint;private String accessKeyId;private String accessKeySecret;private String bucketName;}3).生成OSS工具类对象在sky-server
/*** 配置类用于创建AliOssUtil对象*/
Configuration
Slf4j
public class OssConfiguration {BeanConditionalOnMissingBean //检查IoC容器如果没有此对象再创建public AliOssUtil aliOssUtil(Autowired AliOssProperties aliOssProperties){log.info(开始创建阿里云文件上传工具类对象{},aliOssProperties);return new AliOssUtil(aliOssProperties.getEndpoint(),aliOssProperties.getAccessKeyId(),aliOssProperties.getAccessKeySecret(),aliOssProperties.getBucketName());}
}AliOssUtil.java在sky-common模块中定义即可
这个是阿里云官方提供的API只需完成响应的配置即可。Data
AllArgsConstructor
Slf4j
public class AliOssUtil {private String endpoint;private String accessKeyId;private String accessKeySecret;private String bucketName;/*** 文件上传** param bytes* param objectName* return*/public String upload(byte[] bytes, String objectName) {// 创建OSSClient实例。OSS ossClient new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);try {// 创建PutObject请求。ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));} catch (OSSException oe) {System.out.println(Caught an OSSException, which means your request made it to OSS, but was rejected with an error response for some reason.);System.out.println(Error Message: oe.getErrorMessage());System.out.println(Error Code: oe.getErrorCode());System.out.println(Request ID: oe.getRequestId());System.out.println(Host ID: oe.getHostId());} catch (ClientException ce) {System.out.println(Caught an ClientException, which means the client encountered a serious internal problem while trying to communicate with OSS, such as not being able to access the network.);System.out.println(Error Message: ce.getMessage());} finally {if (ossClient ! null) {ossClient.shutdown();}}//文件访问路径规则 https://BucketName.Endpoint/ObjectNameStringBuilder stringBuilder new StringBuilder(https://);stringBuilder.append(bucketName).append(.).append(endpoint).append(/).append(objectName);log.info(文件上传到:{}, stringBuilder.toString());return stringBuilder.toString();}
}4).定义文件上传接口/*** 通用接口*/
RestController
RequestMapping(/admin/common)
Api(tags 通用接口)
Slf4j
public class CommonController {Autowiredprivate AliOssUtil aliOssUtil;/*** 文件上传* param file* return*/PostMapping(/upload)ApiOperation(文件上传)public ResultString upload(MultipartFile file){log.info(文件上传{},file);try {//原始文件名String originalFilename file.getOriginalFilename();//截取原始文件名的后缀abc.dfdfdf.pngString extension originalFilename.substring(originalFilename.lastIndexOf(.));//构造新文件名称String objectName UUID.randomUUID().toString() extension;//文件的请求路径String filePath aliOssUtil.upload(file.getBytes(), objectName);return Result.success(filePath);} catch (IOException e) {log.error(文件上传失败{}, e.getMessage());}return Result.error(MessageConstant.UPLOAD_FAILED);}
}6、事务处理
按照产品原型中的要求删除实现比较复杂
- 起售中的菜品(status1)不能删除
- 被套餐关联的菜品(setmeal_dish表)不能删除
- 删除菜品后关联的口味数据(dish_flavor表)也需要删除掉//1、检查菜品的状态如果启售不能删除//2、检查菜品是否被套餐引用如果引用不能删除//3、检查通过后删除菜品基本信息和口味信息Transactional//事务
public void deleteBatch(ListLong ids) {//判断当前菜品是否能够删除---是否存在起售中的菜品for (Long id : ids) {Dish dish dishMapper.getById(id);//后绪步骤实现if (dish.getStatus().equals(StatusConstant.ENABLE)) {//当前菜品处于起售中不能删除throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);}}//上述检查方式可以进行如下优化//另外一种判断方式SELECT id FROM dish WHERE id IN (51, 52, 53) AND STATUS 1//select * from dish where id 51//select * from dish where id 52//select * from dish where id 53//for循环外边//ListLong SELECT id FROM dish WHERE id IN (51, 52, 53) and status 1//可自行实现//判断当前菜品是否能够删除---是否被套餐关联了//select setmeal_id from setmeal_dish where dish_id IN (52,54,56)ListLong setmealIds setmealDishMapper.getSetmealIdsByDishIds(ids);if (setmealIds ! null setmealIds.size() 0) {//当前菜品被套餐关联了不能删除throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);}//删除菜品表中的菜品数据for (Long id : ids) {dishMapper.deleteById(id);//后绪步骤实现//删除菜品关联的口味数据dishFlavorMapper.deleteByDishId(id);//后绪步骤实现}}三、苍穹外卖3
1、Redis Redis是一个基于内存的key-value结构数据库。Redis 是互联网技术领域使用最为广泛的存储中间件。**官网**https://redis.io
**中文网**https://www.redis.net.cn/
**key-value结构存储****主要特点**- 基于内存存储读写性能高
- 适合存储**热点数据**热点商品、资讯、新闻
- 企业应用广泛Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库官方提供的数据是可以达到100000的QPS每秒内查询10W次。它存储的value类型比较丰富也被称为结构化的NoSql数据库。NoSqlNot Only SQL不仅仅是SQL泛指**非关系型数据库**。NoSql数据库并不是要取代关系型数据库而是关系型数据库的补充。**关系型数据库(RDBMS)**
- Mysql
- Oracle
- DB2
- SQLServer**非关系型数据库(NoSql)**
- Redis
- Mongo db
- MemCached
Redis安装包分为windows版和Linux版- Windows版下载地址https://github.com/microsoftarchive/redis/releases
- Linux版下载地址 https://download.redis.io/releases/ Redis的安装
1在Windows中安装Redis(项目中使用)
Redis的Windows版属于**绿色软件**直接解压即可使用。
redis-cli.exe: Redis客户端redis-client2在Linux中安装Redis(简单了解) - 后续学完Linux课程再操作- mac系统安装和Linux一样- mac不想自己编译安装参考https://blog.csdn.net/realize_dream/article/details/106227622在Linux系统安装Redis步骤1. 将Redis安装包上传到Linux
2. 解压安装包命令tar -zxvf redis-4.0.0.tar.gz -C /usr/local
3. 安装Redis的依赖环境gcc命令yum install gcc-c
4. 进入/usr/local/redis-4.0.0进行编译命令make
5. 进入redis的src目录进行安装命令make install安装后重点文件说明- /usr/local/redis-4.0.0/src/redis-serverRedis服务启动脚本
- /usr/local/redis-4.0.0/src/redis-cliRedis客户端脚本
- /usr/local/redis-4.0.0/redis.confRedis配置文件2、Redis服务启动与停止
服务启动命令#手动指定配置文件
redis-server.exe redis.windows.confRedis服务默认端口号为 **6379** 通过快捷键**Ctrl C** 即可停止Redis服务当Redis服务启动成功后可通过客户端进行连接。客户端连接命令 : redis-cli.exe
通过redis-cli.exe命令默认连接的是本地的redis服务并且使用默认6379端口。也可以通过指定如下参数连接
redis-cli.exe -h 127.0.0.1 -p 6379 -a 123456
- -h ip地址
- -p 端口号
- -a 密码如果需要设置Redis服务密码修改redis.windows.conf
#requirepass password
requirepass 123456**注意**
- **修改密码后需要重启Redis服务才能生效**
- Redis配置文件中 # 表示注释重启Redis后再次连接Redis时需加上密码否则**能连接成功但无法执行指令**
redis-cli.exe -h localhost -p 6379 -a 123456
#a: auth校验
此时-h 和 -p 参数可省略不写。3、Redis客户端图形化工具及数据类型
默认提供的客户端连接工具界面不太友好同时操作也较为麻烦接下来引入一个Redis客户端图形工具。安装完毕后直接双击启动mac或者linux下载地址https://goanother.com/cn/#downloadRedis存储的是key-value结构的数据其中**key一般使用字符串**value有**5种常用**的数据类型- 字符串 string
- 哈希 hash
- 列表 list
- 集合 set
- 有序集合 sorted set / zset
- bitmap 位图类型
- geo 地理位置类型
- HyperLogLog 基数统计类型- 字符串(string)普通字符串Redis中最简单的数据类型
- 哈希(hash)也叫散列类似于Java中的HashMap结构
- 列表(list)按照插入顺序排序可以有重复元素类似于Java中的LinkedList
- 集合(set)无序集合没有重复元素类似于Java中的HashSet
- 有序集合(sorted set/zset)集合中每个元素关联一个分数(score)根据分数升序排序没有重复元素new HashMapString, 常用的5种类型();1、new HashMapString, String();
2、new HashMapString, new HashMap();
3、new HashMapString, new ArrayList();
4、new HashMapString, new HashSet();
5、new HashMapString, new 有序set();4、Redis常用命令
Redis 中字符串类型常用命令- SET key value 设置指定key的值
- GET key 获取指定key的值
- SETE key seconds value 设置指定key的值并将 key 的过期时间设为 seconds 秒set ex(expire:过期)- SETNX key value 只有在 key不存在时设置 key 的值set nx(not exist:不存在)更多命令可以参考Redis中文网https://www.redis.net.cn哈希操作Redis hash 是一个string类型的 field 和 value 的映射表hash特别适合用于存储对象常用命令- HSET key field value 将哈希表 key 中的字段 field 的值设为 valuehset: hash set- HGET key field 获取存储在哈希表中指定字段的值hget: hast get- HDEL key field 删除存储在哈希表中的指定字段- HKEYS key 获取哈希表中所有字段- HVALS key 获取哈希表中所有值hvals: hash values- HGETALL key 获取在哈希表中指定 key 的所有字段和值hgetall: hast get all企业开发中常用格式hset students 101 {name:zhangsan, age:20}
hset students 102 {name:lisi, age:25}列表list操作Redis 列表是简单的字符串列表按照插入顺序排序常用命令LPUSH key value1 [value2] 将一个或多个值插入到列表头部lpush mylist d c b a
#list push中可以保存相同的元素
#lpush mylist a b
#right push: rpushLRANGE key start stop 获取列表指定范围内的元素
lrange mylist 0 -1
#-1代表list中最后一个元素的位置- RPOP key 移除并获取列表最后一个元素- LLEN key 获取列表长度- BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素 如果列表没有元素会阻塞直到等待超时或发现可弹出元素为止brpop: block right pop集合set操作
Redis set 是string类型的无序集合。集合成员是唯一的这就意味着集合中不会出现重复的数据常用命令- SADD key member1 [member2] 向集合添加一个或多个成员
sadd myset a b c d a b- SMEMBERS key 返回集合中的所有成员
SMEMBERS myset
#返回结果a b c d
#发现没有重复的元素- SCARD key 获取集合的成员数- SINTER key1 [key2] 返回给定所有集合的交集
sadd myset2 c d e f#求myset和myset2的交集两个集合中都有的元素
sinter myset myset2
#会输出c d - SUNION key1 [key2] 返回所有给定集合的并集
sunion myset myset2
#会输出a b c d e f- SREM key member1 [member2] 移除集合中一个或多个成员
#srem: set remove
srem myset a b 有序集合zset操作Redis有序集合是string类型元素的集合且不允许有重复成员。每个元素都会关联一个double类型的分数(score)。redis正是通过分数来为集合中的成员进行从小到大排序。有序集合的成员是唯一的但分数却可以重复。常用命令- ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员或者更新已存在成员的 分数
zadd myzset 80 wangwu
zadd myzset 100 zhangsan 60 lisi- ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合中指定区间内的成员
#默认是升序从小到大(根据score)
zrange myzset 0 -1
#1) lisi
#2) wangwu
#3) zhangsan#如果想倒序zrevrange key start stop [WITHSCORES]
#revert:反转
zrevrange myzset 0 -1
#1) zhangsan
#2) wangwu
#3) lisi- ZINCRBY key increment member 有序集合中对指定成员的分数加上增量 increment
#给wangwu增加30分
#increment增长
zincrby myzset 30 wangwu
zrevrange myzset 0 -1- ZREM key member [member ...] 移除有序集合中的一个或多个成员通用命令
Redis的通用命令是不分数据类型的都可以使用的命令- KEYS pattern 查找所有符合给定模式( pattern)的 key - EXISTS key 检查给定 key 是否存在- TYPE key 返回 key 所储存的值的类型- DEL key 该命令用于在 key 存在是删除 key- TTL key 返回给定 key 的剩余生存时间(TTL, time to live)以秒为单位共有3种返回结果
-1: 没有过期时间永久有效
-2已经过期或者不存在这个一个key
正数代表还能在内存中存活的时间单位秒5、Spring Data Redis使用方式
Redis 的 Java 客户端很多常用的几种- Jedis 在java中操作Redis- Lettuce- Spring Data RedisSpring 对 Redis 客户端进行了整合提供了 Spring Data Redis在Spring Boot项目中还提供了对应的Starter即 spring-boot-starter-data-redis。重点学习Spring Data Redis。1).导入Spring Boot提供了对应的Startermaven坐标
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependencySpring Data Redis中提供了一个高度封装的类RedisTemplate对相关api进行了归类封装,将同一类型操作封装为operation接口具体分类如下- ValueOperationsstring数据操作- SetOperationsset类型数据操作- ZSetOperationszset类型数据操作- HashOperationshash类型的数据操作- ListOperationslist类型的数据操作2). 配置Redis数据源
#sky:redis:host: localhostport: 6379#设置密码没有设置则注释掉password: 123456database: 0database:指定使用Redis的哪个数据库Redis服务启动后默认有16个数据库编号分别是从0到15
#可以使用select 切换不同的数据库
select 0
select 1在application.yml中添加读取application-dev.yml中的相关Redis配置spring:profiles:active: devredis:host: ${sky.redis.host}port: ${sky.redis.port}password: ${sky.redis.password}database: ${sky.redis.database}3). 编写配置类创建RedisTemplate对象Configuration
Slf4j
public class RedisConfiguration {Beanpublic RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){log.info(开始创建redis模板对象...);RedisTemplate redisTemplate new RedisTemplate();//设置redis的连接工厂对象redisTemplate.setConnectionFactory(redisConnectionFactory);//设置redis key的序列化器redisTemplate.setKeySerializer(new StringRedisSerializer());return redisTemplate;}
}/*********************************************************************************/
当前配置类不是必须的因为 Spring Boot 框架会自动装配 RedisTemplate 对象RedisAutoConfiguration但是默认的key序列化器为JdkSerializationRedisSerializer导致我们存到Redis中后的数据和原始数据有差别故设置为StringRedisSerializer序列化器。//jar包中RedisAutoConfiguration类的源码
Bean
ConditionalOnMissingBean(name redisTemplate)
ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplateObject, Object redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplateObject, Object template new RedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;
}4). 通过RedisTemplate对象操作RedisSpringBootTest
public class SpringDataRedisTest {Autowiredprivate RedisTemplate redisTemplate;Test //注意使用长的import org.junit.jupiter.api.Test;public void testRedisTemplate(){System.out.println(redisTemplate);//string数据操作ValueOperations valueOperations redisTemplate.opsForValue();//hash类型的数据操作HashOperations hashOperations redisTemplate.opsForHash();//list类型的数据操作ListOperations listOperations redisTemplate.opsForList();//set类型数据操作SetOperations setOperations redisTemplate.opsForSet();//zset类型数据操作ZSetOperations zSetOperations redisTemplate.opsForZSet();}
}
6、测试实例
1). 操作字符串类型数据//set name 小明redisTemplate.opsForValue().set(name,小明);//get nameString city (String) redisTemplate.opsForValue().get(name);System.out.println(city);
2). 操作哈希类型数据HashOperations hashOperations redisTemplate.opsForHash();hashOperations.put(100,name,tom);hashOperations.put(100,age,20);String name (String) hashOperations.get(100, name);System.out.println(name);
3). 操作列表类型数据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);
4). 操作集合类型数据SetOperations setOperations redisTemplate.opsForSet();setOperations.add(set1,a,b,c,d);setOperations.add(set2,a,b,x,y);Set members setOperations.members(set1);System.out.println(members);Long size setOperations.size(set1);System.out.println(size);
5). 操作有序集合类型数据ZSetOperations zSetOperations redisTemplate.opsForZSet();zSetOperations.add(zset1,a,10);zSetOperations.add(zset1,c,9);//默认升序Set zset1 zSetOperations.range(zset1, 0, -1);System.out.println(zset1);//降序进行反转reversezset1 zSetOperations.reverseRange(zset1, 0, -1);System.out.println(zset1);
6). 通用命令操作Set keys redisTemplate.keys(*);System.out.println(keys);//是否存在haveBoolean name redisTemplate.hasKey(name);System.out.println(是否存在name: name);Boolean set1 redisTemplate.hasKey(set1);System.out.println(是否存在set1: set1);for (Object key : keys) {DataType type redisTemplate.type(key);System.out.println(type.name());}redisTemplate.delete(mylist);//查询剩余存活时间time to live//expire: 过期Long ttl redisTemplate.getExpire(set1);System.out.println(ttl);7、数据同步 为了保证数据库和Redis中的数据保持一致修改管理端接口 DishController的相关方法加入清理缓存逻辑。- 注意在使用缓存过程中要注意保证数据库中的数据和缓存中的数据一致
- 如果MySQL中的数据发生变化需要及时清理缓存数据。否则就会造成缓存数据与数据库数据不一致的情况。
抽取清理缓存的方法在管理端DishController中添加
新增菜品优化PostMappingApiOperation(新增菜品)public Result save(RequestBody DishDTO dishDTO) {log.info(新增菜品{}, dishDTO);dishService.saveWithFlavor(dishDTO);//新增代码//清理缓存数据String key dish_ dishDTO.getCategoryId();cleanCache(key);return Result.success();}
菜品批量删除优化
//新增代码//将所有的菜品缓存数据清理掉所有以dish_开头的keycleanCache(dish_*);
修改菜品优化//新增代码cleanCache(dish_ dishDTO.getCategoryId());
菜品起售停售优化
//新增代码//将所有的菜品缓存数据清理掉所有以dish_开头的keycleanCache(dish_*);8、Spring Cache 框架
Spring Cache 是一个框架实现了基于注解的缓存功能只需要简单地加一个注解就能实现缓存功能。- Spring Cache 提供了一层抽象底层可以切换不同的缓存实现例如
- EHCache
- Caffeine
- Redis(常用)
起步依赖dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-cache/artifactId version2.7.3/version
/dependency常用的注解注解说明EnableCaching开启缓存注解功能通常加在启动类上Cacheable在方法执行前先查询缓存中是否有数据如果有数据则直接返回缓存数据如果没有缓存数据调用方法并将方法返回值放到缓存中CachePut将方法的返回值放到缓存中CacheEvict将一条或多条数据从缓存中删除 在spring boot项目中使用缓存技术只需在项目中导入相关缓存技术的依赖包并在启动类上使用EnableCaching开启缓存支持即可。使用Redis作为缓存技术只需要导入Spring data Redis的maven坐标即可。创建名为spring_cache_demo数据库将springcachedemo.sql脚本直接导入数据库中。
引导类上加EnableCaching:Slf4j
SpringBootApplication
EnableCaching//开启缓存注解功能
public class CacheDemoApplication {public static void main(String[] args) {SpringApplication.run(CacheDemoApplication.class,args);log.info(项目启动成功...);}
}检查密码application.yml#数据库密码和Redis密码
spring:datasource:druid:username: rootpassword: rootredis:host: localhostport: 6379password: 1234562). CachePut注解CachePut 说明 作用: 将方法返回值放入缓存 value: 缓存的名称, 每个缓存名称下面可以有很多key key: 缓存的key ---------- 支持Spring的表达式语言SPEL语法在save方法上加注解CachePut
当前UserController的save方法是用来保存用户信息的在该用户信息保存到数据库的同时也往缓存中缓存一份数据我们可以在save方法上加上注解 CachePut用法如下/*CachePut将方法返回值放入缓存value缓存的名称每个缓存名称下面可以有多个keykey缓存的key*/PostMapping//CachePut(value userCache, key #resutl.id)CachePut(value userCache, key #user.id)//key的生成userCache::1public User save(RequestBody User user){userMapper.insert(user);return user;}#user.id : #user指的是方法形参的名称, id指的是user的id属性 , 也就是使用user的id属性作为key ;
#result.id : #result代表方法返回值该表达式 代表以返回对象的id属性作为key 了解#p0.id#p0指的是方法中的第一个参数id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key
p: param#a0.id#a0指的是方法中的第一个参数id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key
a: argument#root.args[0].id#root.args[0]指的是方法中第一个参数id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key 3). Cacheable注解作用: 在方法执行前spring先查看缓存中是否有数据如果有数据则直接返回缓存数据若没有数据调用方法并将方法返回值放到缓存中 value: 缓存的名称每个缓存名称下面可以有多个key key: 缓存的key ---------- 支持Spring的表达式语言SPEL语法在getById上加注解Cacheable/*** Cacheable在方法执行前spring先查看缓存中是否有数据如果有数据则直接返回缓存数据若没有数据 *调用方法并将方法返回值放到缓存中* value缓存的名称每个缓存名称下面可以有多个key* key缓存的key*/GetMappingCacheable(cacheNames userCache,key#id)public User getById(Long id){User user userMapper.getById(id);return user;}4). CacheEvict注解CacheEvict 说明作用: 清理指定缓存 value: 缓存的名称每个缓存名称下面可以有多个key key: 缓存的key ---------- 支持Spring的表达式语言SPEL语法
在 delete 方法上加注解CacheEvictDeleteMappingCacheEvict(cacheNames userCache,key #id)//删除某个key对应的缓存数据public void deleteById(Long id){userMapper.deleteById(id);}DeleteMapping(/delAll)CacheEvict(cacheNames userCache,allEntries true)//删除userCache下所有的缓存数据public void deleteAll(){userMapper.deleteAll();}5). 缓存非null值【扩展】思考如果getById时查询的是一个不存在的用户那么会缓存一个null值此时就毫无意义该如何判断下如果是null就不缓存呢condition : 表示满足条件, 再进行缓存 ;
unless : 表示满足条件则不缓存 ; 与上述的condition是反向的 ;/*** Cacheable在方法执行前spring先查看缓存中是否有数据如果有数据则直接返回缓存数据若没有数据调用方法并将方法返回值放到缓存中* value缓存的名称每个缓存名称下面可以有多个key* key缓存的key* condition条件满足条件时才缓存数据* unless满足条件则不缓存*/
Cacheable(value userCache,key #id, unless #result null)
GetMapping(/{id})
public User getById(PathVariable Long id){User user userService.getById(id);return user;
}#注意
#此处我们只能使用 unless什么时候不存储
#1.condition中只能使用方法参数中数据如果使用#result不管是否查询到数据都是返回null
#2.unless才能使用方法返回值中的数据四、苍穹外卖(小程序端)
1.HttpClient #mermaid-svg-RDxp7503ylDjXCNd {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-RDxp7503ylDjXCNd .error-icon{fill:#552222;}#mermaid-svg-RDxp7503ylDjXCNd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RDxp7503ylDjXCNd .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-RDxp7503ylDjXCNd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RDxp7503ylDjXCNd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RDxp7503ylDjXCNd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RDxp7503ylDjXCNd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RDxp7503ylDjXCNd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RDxp7503ylDjXCNd .marker.cross{stroke:#333333;}#mermaid-svg-RDxp7503ylDjXCNd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RDxp7503ylDjXCNd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RDxp7503ylDjXCNd .cluster-label text{fill:#333;}#mermaid-svg-RDxp7503ylDjXCNd .cluster-label span{color:#333;}#mermaid-svg-RDxp7503ylDjXCNd .label text,#mermaid-svg-RDxp7503ylDjXCNd span{fill:#333;color:#333;}#mermaid-svg-RDxp7503ylDjXCNd .node rect,#mermaid-svg-RDxp7503ylDjXCNd .node circle,#mermaid-svg-RDxp7503ylDjXCNd .node ellipse,#mermaid-svg-RDxp7503ylDjXCNd .node polygon,#mermaid-svg-RDxp7503ylDjXCNd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RDxp7503ylDjXCNd .node .label{text-align:center;}#mermaid-svg-RDxp7503ylDjXCNd .node.clickable{cursor:pointer;}#mermaid-svg-RDxp7503ylDjXCNd .arrowheadPath{fill:#333333;}#mermaid-svg-RDxp7503ylDjXCNd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RDxp7503ylDjXCNd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RDxp7503ylDjXCNd .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-RDxp7503ylDjXCNd .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-RDxp7503ylDjXCNd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RDxp7503ylDjXCNd .cluster text{fill:#333;}#mermaid-svg-RDxp7503ylDjXCNd .cluster span{color:#333;}#mermaid-svg-RDxp7503ylDjXCNd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RDxp7503ylDjXCNd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客户端 发送Http请求 服务端 接受响应数据 HttpClient 是Apache Jakarta Common 下的子项目可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包并且它支持 HTTP 协议最新的版本和建议。优点支持连接池、多线程HttpClient作用- 发送HTTP请求
- 接收响应数据HttpClient应用场景当我们在使用扫描支付、查看地图、获取验证码、查看天气等功能时。应用程序本身并未实现这些功能都是在应用程序里访问提供这些功能的服务访问这些服务需要发送HTTP请求并且接收响应数据可通过HttpClient来实现。‘HttpClient的maven坐标dependencygroupIdorg.apache.httpcomponents/groupIdartifactIdhttpclient/artifactIdversion4.5.13/version
/dependency
2、HttpClient的核心API
- HttpClientHttp客户端对象类型使用该类型对象可发起Http请求。
- HttpClients可认为是构建器可创建HttpClient对象。
- CloseableHttpClient实现类实现了HttpClient接口。
- HttpGetGet方式请求类型。
- HttpPostPost方式请求类型。
- put
- deleteHttpClient发送请求步骤- 创建HttpClient对象
- 创建Http请求对象
- 调用HttpClient的execute方法发送请求其他OkHttp、RestTemplateSpringMVC……应该导入HttpClient相关的坐标
dependencygroupIdcom.aliyun.oss/groupIdartifactIdaliyun-sdk-oss/artifactId
/dependency
!--阿里云上传底层是用HttpClient将上传的请求发送到阿里云服务器--1、GET方式请求SpringBootTest
public class HttpClientTest {/*** 测试通过httpclient发送GET方式的请求*/Testpublic void testGET() throws Exception{//创建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();}
}2.POST方式请求/*** 测试通过httpclient发送POST方式的请求*/
Test
public void testPOST() throws Exception{// 创建httpclient对象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);//jsonObject.toString() {username: admin, 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);//关闭资源response.close();httpClient.close();
}3、微信小程序开发
- 注册小程序- 完善小程序信息-不能选择小游戏类目- 下载开发者工具 1). 注册小程序
注册地址https://mp.weixin.qq.com/wxopen/waregister?actionstep12). 完善小程序信息登录小程序后台https://mp.weixin.qq.com/完善小程序信息、小程序类目,不能选择小游戏类目,查看小程序的 AppID和 AppSecret3). 下载开发者工具其他系统下载地址 https://developers.weixin.qq.com/miniprogram/dev/devtools/stable.html 熟悉开发者工具布局
设置不校验合法域名
**注**开发阶段小程序发出请求到后端的Tomcat服务器若不勾选请求发送失败。- 小程序目录结构小程序包含一个描述整体程序的 app 和多个描述各自页面的 page。主体部分由三个文件组成必须放在项目的根目录。文件说明- app.js 必须存在主要存放小程序的逻辑代码- app.json必须存在小程序配置文件主要存放小程序的公共配置- app.wxss: 非必须存在主要存放小程序公共样式表类似于前端的CSS样式- js文件必须存在存放页面业务逻辑代码编写的js代码。- json文件非必须存放页面相关的配置。- wxml文件必须存在存放页面结构主要是做页面布局页面效果展示的类似于HTML页面。- wxss文件非必须存放页面样式表相当于CSS文件。4、编写和编译小程序
1). 编写
进入到index.wxml编写页面布局view classcontainerview{{msg}}/viewviewbutton typedefault bindtapgetUserInfo获取用户信息/buttonimage stylewidth: 100px;height: 100px; src{{avatarUrl}}/image{{nickName}}/viewviewbutton typeprimary bindtapwxlogin微信登录/button授权码{{code}}/viewviewbutton typewarn bindtapsendRequest发送请求/button响应结果{{result}}/view
/view进入到index.js编写业务逻辑代码Page({data:{msg:hello world,avatarUrl:,nickName:,code:,result:},getUserInfo:function(){wx.getUserProfile({desc: 获取用户信息,success:(res) {console.log(res.userInfo)this.setData({avatarUrl:res.userInfo.avatarUrl,nickName:res.userInfo.nickName})}})},wxlogin:function(){wx.login({success: (res) {console.log(授权码res.code)this.setData({code:res.code})}})},sendRequest:function(){//通过微信小程序向项目服务器Tomcat发送一个请求wx.request({url: http://localhost:8080/user/shop/status,method:GET,success:(res) {console.log(响应结果 res.data.data)this.setData({result:res.data.data})}})}})点击编译按钮进行运行
为了防止小程序开发者滥用用户昵称和头像官方停用了接口如果想看效果需要切换到旧版基础库。5、发布小程序
小程序的代码都已经开发完毕要将小程序发布上线让所有的用户都能使用到这个小程序。点击上传按钮指定版本号:上传成功
当前小程序版本只是一个开发版本。
进到微信公众平台打开版本管理页面。
需提交审核变成审核版本审核通过后进行发布变成线上版本。6、导入小程序代码
开发微信小程序本质上是属于前端的开发
1). 找到资料
2). 导入代码
AppID使用自己的AppID3). 查看项目结构
主体的文件:app.js app.json app.wxss
项目的页面比较多主要存放在pages目录。4). 修改配置
因为小程序要请求后端服务需要修改为自己后端服务的ip地址和端口号(默认不需要修改)
common--vendor.js--搜索(ctrlf)--baseUri- 微信登录流程微信登录https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html1. 小程序端调用wx.login()获取code就是授权码。
2. 小程序端调用wx.request()发送请求并携带code请求开发者服务器(自己编写的后端服务)。
3. 开发者服务端通过HttpClient向微信接口服务发送请求并携带appIdappsecretcode三个参数。
4. 开发者服务端接收微信接口服务返回的数据session_keyopendId等。opendId是微信用户的唯一标识。
5. 开发者服务端自定义登录态生成令牌(token)和openid等数据返回给小程序端方便后绪请求身份校验。
6. 小程序端收到自定义登录态存储storage。
7. 小程序端后绪通过wx.request()发起业务请求时携带token。
8. 开发者服务端收到请求后通过携带的token解析当前登录用户的id。
9. 开发者服务端身份校验通过后继续相关的业务逻辑处理最终返回业务数据。说明
10. 调用https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html 获取 临时登录凭证code 并回传到开发者服务器。
11. 调用https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html 接口
换取 用户唯一标识 OpenID 、 用户在微信开放平台帐号下的唯一标识UnionID若当前小程序已绑定到微信开放平台帐号 和 会话密钥 session_key。7、配置微信登录所需配置项
定义相关配置配置微信登录所需配置项
application-dev.yml#sky: 改成自己的小程序id和secretwechat:appid: wxffb3637a228223b8secret: 84311df9199ecacdf4f12d27b6b9522dapplication.yml#sky:wechat:appid: ${sky.wechat.appid}secret: ${sky.wechat.secret}配置为微信用户生成jwt令牌时使用的配置项
application.yml#sky:#jwt:# 省略......user-secret-key: itheimauser-ttl: 7200000user-token-name: authentication用户端拦截器/*** jwt令牌校验的拦截器*/
Component
Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {Autowiredprivate JwtProperties jwtProperties;/*** 校验jwt** param request* param response* param handler* return* throws Exception*/public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断当前拦截到的是Controller的方法还是其他资源////注意HandlerMethod导包org.springframework.web.method.HandlerMethodif (!(handler instanceof org.springframework.web.method.HandlerMethod)) {//当前拦截到的不是动态方法直接放行return true;}//1、从请求头中获取令牌String token request.getHeader(jwtProperties.getUserTokenName());//2、校验令牌try {log.info(jwt校验:{}, token);Claims claims JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);Long userId Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());log.info(当前用户的id{}, userId);BaseContext.setCurrentId(userId); //放到ThreadLocal里边//3、通过放行return true;} catch (Exception ex) {//4、不通过响应401状态码response.setStatus(401);return false;}}
}
/***************************************************************************************************/在WebMvcConfiguration配置类中注册拦截器
Autowired
private JwtTokenUserInterceptor jwtTokenUserInterceptor;/*** 注册自定义拦截器* param registry*/protected void addInterceptors(InterceptorRegistry registry) {log.info(开始注册自定义拦截器...);//.........registry.addInterceptor(jwtTokenUserInterceptor).addPathPatterns(/user/**).excludePathPatterns(/user/user/login).excludePathPatterns(/user/shop/status);
}8、订单支付 要实现微信支付就需要注册微信支付的一个商户号这个商户号是必须要有一家企业并且有正规的营业执照。只有具备了这些资质之后才可以去注册商户号才能开通支付权限。微信支付产品本项目选择小程序支付参考https://pay.weixin.qq.com/static/product/product_index.shtml
微信小程序支付时序图微信支付相关接口
JSAPI下单商户系统调用该接口在微信支付服务后台生成预支付交易单(对应时序图的第5步)- https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml微信小程序调起支付通过JSAPI下单接口获取到发起支付的必要参数prepay_id然后使用微信支付提供的小程序方法调起小程序支付(对应时序图的第10步)- https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_4.shtml
完成微信支付有两个关键的步骤- 第一个 就是需要在商户系统当中调用微信后台的一个下单接口就是生成预支付交易单。- 第二个 就是支付成功之后微信后台会给推送消息。
解决微信提供的方式就是对数据进行加密、解密、签名多种方式。要完成数据加密解密需要提前准备相应的一些文件其实就是一些证书。获取微信支付平台证书、商户私钥文件.目前商户系统它的ip地址就是当前自己电脑的ip地址只是一个局域网内的ip地址微信后台无法调用到。**内网穿透**通过cpolar软件可以获得一个临时域名而这个临时域名是一个公网ip这样微信后台就可以请求到商户系统。
cpolar软件的使用1). 下载与安装下载地址https://dashboard.cpolar.com/get-started2). cpolar指定authtoken
复制authtokenhttps://dashboard.cpolar.com/auth执行命令
#Windows:
cpolar.exe authtoken MmJiMTBiZDAtMz333330ZWI5LTlhOTQtODE1ZjcxNmZhOGRl3). 获取临时域名执行命令cpolar.exe http 80804). 验证临时域名有效性启动项目访问接口文档http://localhost:8080/doc.html 使用临时域名访问,证明临时域名生效**微信支付相关配置**#sky:#wechat:#appid: wxffb3637a228223b8#secret: 84311df9199ecacdf4f12d27b6b9522d#支付需要用到的配置mchid : 1561414331 #支付商户号mchSerialNo: 4B3B3DC35414AD50B1B755BAF8DE9CC7CF407606 #商户编号apiV3Key: CZBK51236435wxpay435434323FFDuv3privateKeyFilePath: D:\apiclient_key.pemweChatPayCertFilePath: D:\wechatpay_166D96F876F45C7D07CE98952A96EC980368ACFC.pem#设置微信支付回调接口notifyUrl: http://5c968a9a.r1.cpolar.top/notify/paySuccess#退款回调接口refundNotifyUrl: https://www.weixin.qq.com/wxpay/refundapplication.yml
#sky:#wechat:#appid: ${sky.wechat.appid}#secret: ${sky.wechat.secret}#微信支付需要用到的配置mchid : ${sky.wechat.mchid}mchSerialNo: ${sky.wechat.mchSerialNo}privateKeyFilePath: ${sky.wechat.privateKeyFilePath}apiV3Key: ${sky.wechat.apiV3Key}weChatPayCertFilePath: ${sky.wechat.weChatPayCertFilePath}notifyUrl: ${sky.wechat.notifyUrl}refundNotifyUrl: ${sky.wechat.refundNotifyUrl}WeChatProperties.java读取配置Component
ConfigurationProperties(prefix sky.wechat)
Data
public class WeChatProperties {private String appid; //小程序的appidprivate String secret; //小程序的秘钥private String mchid; //商户号private String mchSerialNo; //商户API证书的证书序列号private String privateKeyFilePath; //商户私钥文件private String apiV3Key; //证书解密的密钥private String weChatPayCertFilePath; //平台证书private String notifyUrl; //支付成功的回调地址private String refundNotifyUrl; //退款成功的回调地址
}Service层在OrderService.java中添加payment和paySuccess两个方法定义/*** 订单支付* param ordersPaymentDTO* return*/OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception;/*** 获取支付结果修改订单状态* param outTradeNo*/void paySuccess(String outTradeNo);
/*************************************************************************/
PayNotifyController.java/*** 支付回调相关接口*/
RestController
RequestMapping(/notify)
Slf4j
public class PayNotifyController {Autowiredprivate OrderService orderService;Autowiredprivate WeChatProperties weChatProperties;/*** 支付成功回调** param request*/RequestMapping(/paySuccess)public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {//读取数据String body readData(request);log.info(支付成功回调{}, body);//数据解密String plainText decryptData(body);log.info(解密后的文本{}, plainText);JSONObject jsonObject JSON.parseObject(plainText);String outTradeNo jsonObject.getString(out_trade_no);//商户平台订单号String transactionId jsonObject.getString(transaction_id);//微信支付交易号log.info(商户平台订单号{}, outTradeNo);log.info(微信支付交易号{}, transactionId);//业务处理修改订单状态、来单提醒orderService.paySuccess(outTradeNo);//给微信响应responseToWeixin(response);}/*** 读取数据** param request* return* throws Exception*/private String readData(HttpServletRequest request) throws Exception {BufferedReader reader request.getReader();StringBuilder result new StringBuilder();String line null;while ((line reader.readLine()) ! null) {if (result.length() 0) {result.append(\n);}result.append(line);}return result.toString();}/*** 数据解密** param body* return* throws Exception*/private String decryptData(String body) throws Exception {JSONObject resultObject JSON.parseObject(body);JSONObject resource resultObject.getJSONObject(resource);String ciphertext resource.getString(ciphertext);String nonce resource.getString(nonce);String associatedData resource.getString(associated_data);AesUtil aesUtil new AesUtil(weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));//密文解密String plainText aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonce.getBytes(StandardCharsets.UTF_8),ciphertext);return plainText;}/*** 给微信响应* param response*/private void responseToWeixin(HttpServletResponse response) throws Exception{response.setStatus(200);HashMapObject, Object map new HashMap();map.put(code, SUCCESS);map.put(message, SUCCESS);response.setHeader(Content-type, ContentType.APPLICATION_JSON.toString());response.getOutputStream().write(JSONUtils.toJSONString(map).getBytes(StandardCharsets.UTF_8));response.flushBuffer();}
}五、苍穹外卖
1、注册百度地图服务
1. 基于百度地图开放平台实现https://lbsyun.baidu.com/2.注册账号---控制台---我的应用--创建应用获取AK(服务端应用)---调用接口
创建应用时
类型选服务端
#IP白名单0.0.0.0/0 3.相关接口
地理编码服务https://lbsyun.baidu.com/index.php?titlewebapi/guide/webservice-geocoding地理编码服务又名Geocoder是一类Web API接口服务
地理编码服务提供将结构化地址数据如北京市海淀区上地十街十号转换为对应坐标点经纬度功能GET: https://api.map.baidu.com/geocoding/v3/?address北京市海淀区上地十街10号outputjsonak您的ak
在url中传递3个参数即可返回数据格式如下Java中将返回的json字符串转成JSONObject{ // JSONObjectstatus: 0, // jsonObject.getIntValue(status)result: { //对象: jsonObject.getJSONObject(result)location: { //对象: jsonObject.getJSONObject(location) lng: 116.3076223267197, //经度 getString(lng)lat: 40.05682848596073 //纬度 getString(lat)},precise: 1,confidence: 80,comprehension: 100,level: 门址}
}路线规划服务https://lbsyun.baidu.com/index.php?titlewebapi/directionlite-v1轻量级路线规划服务又名DirectionLite API 是一套REST风格的Web服务API以HTTP/HTTPS形式提供了路线规划服务。相较于Direction APIDirectionLite API更注重服务的高性能和接口的轻便简洁满足基础的路线规划需求并不具备Direciton API中的驾车多路线/未来出行和公交跨城规划等高级功能。DirectionLite API支持驾车、骑行、步行、公交路线规划支持中国大陆地区。GET: https://api.map.baidu.com/directionlite/v1/driving?origin40.01116,116.339303destination39.936404,116.452562steps_info0ak您的AK
传递4个参数即可返回数据如下{status: 0, //getIntValuemessage: ok,result: { //对象: getJSONObjectorigin: {lng: 116.33929505188,lat: 40.011157363344},destination: {lng: 116.45255341058,lat: 39.936401378723},routes: [ //数组: result.getJSONArray(routes);{distance: 18129, //getString 得到的两地间的距离duration: 6193}]}
}2、检验配送距离
商家门店地址可以配置在配置文件中例如application.yml
#sky:shop:address: 北京市海淀区上地十街10号baidu:ak: sdfsdfsdfsd #百度应用id用户下单时添加校验代码
//两项用途过少没有必要新建一个配置类
Value(${sky.shop.address})//将yml文件中的属性赋值到shopAddress中
private String shopAddress;Value(${sky.baidu.ak})
private String ak;/*** 检查客户的收货地址是否超出配送范围* param address 收货地址*/
private void checkOutOfRange(String address) {//1、调用百度地理编码服务根据店铺地址获取经纬度String shopGeo getGeoByAddress(shopAddress);//2、调用地理编码服务服务根据用户配送地址获取经纬度String userGeo getGeoByAddress(address);//3、调用路线规划服务对两个地址进行规划根据返回的距离判断是否在配送范围Integer distance getDistance(shopGeo, userGeo);if(distance 5000){//配送距离超过5000米throw new OrderBusinessException(超出配送范围: distance);}
}编写两个私有方法getGeoByAddress和getDistance在方法中用HttpClientUtil.doGet()调用百度接口/*** 根据详细地址获取经纬度坐标* 地理编码服务https://lbsyun.baidu.com/index.php?titlewebapi/gui** param address 详细地址包含省市区* return 经纬度格式为纬度,经度小数点后不超过6位40.056878,116.30815*/
private String getGeoByAddress(String address) {//1、使用map构建请求参数MapString, String map new HashMap();map.put(address,address); //地址map.put(output,json);//指定返回数据格式map.put(ak,ak);//2、调用百度地图服务获取经纬度坐标String json HttpClientUtil.doGet(https://api.map.baidu.com/geocoding/v3, map);log.info(地址: {}返回值{}, address, json);//3、解析响应结果JSONObject jsonObject JSON.parseObject(json);//status0表示成功if(jsonObject.getIntValue(status) ! 0){throw new OrderBusinessException(地址解析失败);}//数据解析JSONObject location jsonObject.getJSONObject(result).getJSONObject(String lat location.getString(lat); //纬度String lng location.getString(lng); //经度//返回经纬度坐标return lat , lng;
}完善下单功能public OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO) {//1、下单数据校验收货地址为空、超出配送范围、购物车为空Long userId BaseContext.getCurrentId();//1.1 收货地址为空AddressBook addressBook addressBookMapper.getById(ordersSubmitDTO.getAddressBookId());if (addressBook null) {throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);}//新增代码//1.2 超出配送范围checkOutOfRange(addressBook.getProvinceName() addressBook.getCityName() addressBook.getDetail());//1.3 购物车为空(userId)//……省略其他代码
}3、定时任务Spring Task
Spring Task是Spring框架提供的任务调度工具可以按照约定的时间自动执行某个代码逻辑。定位定时任务框架
作用定时自动执行某段Java代码应用场景1). 信用卡每月还款提醒2). 银行贷款每月还款提醒3). 火车票售票系统处理未支付订单4). 入职纪念日为用户发送通知**强调只要是需要定时处理的场景都可以使用Spring Task
**cron表达式**- 其实就是一个字符串通过cron表达式可以定义任务触发的时间- 构成规则分为6或7个域由空格分隔开每个域代表一个含义秒、分钟、小时、日、月、周、年(可选)每部分的含义如下表所示:组成部分含义取值范围第一部分Seconds (秒)059第二部分Minutes(分)059第三部分Hours(时)0-23第四部分Day-of-Month(天)1-31第五部分Month(月)0-11或JAN-DEC第六部分Day-of-Week(星期)1-7(1表示星期日)或SUN-SAT第七部分Year(年) 可选1970-2099
cron表达式还可以包含一些特殊符号来设置更加灵活的定时规则, 如下表所示:符号含义?不确定的值。当两个子表达式其中一个被指定了值以后为了避免冲突需要将另外一个的值为“?” 例如想在每月20日触发调度不管20号是星期几只能用如下写法0 0 0 20 * ? 其中最后一个只能用“?”*代表所有可能的值,设置多个值,例如”26,29,33”表示在26分,29分和33分各自运行一次任务-设置取值范围,例如”5-20”表示从5分到20分钟每分钟运行一次任务/设置频率或间隔, 如1/15表示从1分开始,每隔15分钟运行一次任务L用于每月或每周表示每月的最后一天或每个月的最后星期几 例如6L表示每月的最后一个星期五W表示离给定日期最近的工作日 例如15W放在每月day-of-month上表示离本月15日最近的工作日#表示该月第几个周X。例如”6#3”表示该月第3个周五
举个例子- 说明一般日和周的值不同时设置如果其中一个设置了另一个用表示。秒分钟小时日月周年00912102022 这些具体的细节我们就不用自己去手写因为这个cron表达式有在线生成器https://cron.qqe2.com/可以直接在这个网站上面只要根据自己的要求去生成corn表达式即可一般就不用自己去编写这个表达式。**cron表达式案例***/5 * * * * ? 每隔5秒执行一次
0 */1 * * * ? 每隔1分钟执行一次
0 0 5-15 * * ? 每天5-15点整点触发
0 0/3 * * * ? 每三分钟触发一次
0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
0 0 10,14,16 * * ? 每天上午10点下午2点4点 # 使用步骤1). 导入maven坐标 spring-context2). 启动类添加注解 EnableScheduling 开启任务调度3). 自定义定时任务类编写定时任务类在sky-server模块中/*** 自定义定时任务类*/
Component
Slf4j
public class MyTask {/*** 定时任务 每隔5秒触发一次*/Scheduled(cron 0/5 * * * * ?)//Scheduled(fixedRate 5000)public void executeTask(){System.out.println(定时任务开始执行 new Date());}
}4、WebSocket全双工通信 WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手两者之间就可以创建持久性的连接 并进行双向数据传输。**HTTP协议和WebSocket协议对比**- HTTP是短连接WebSocket是长连接
- HTTP通信是单向的基于请求响应模式WebSocket支持双向通信
- 相同之处HTTP和WebSocket底层都是TCP连接
- 服务器可以主动向客户端推送消息。WebSocket缺点- 服务器长期维护长连接需要一定的成本
- WebSocket 是长连接受网络限制比较大需要处理好重连
- 各个浏览器支持程度不一WebSocket并不能完全取代HTTP它只适合在特定的场景下使用1). 直播弹幕2). 网页聊天3). 体育实况更新4). 股票基金报价实时更新**入门案例**- 需求
- 实现浏览器与服务器全双工通信。浏览器既可以向服务器发送消息服务器也可主动向浏览器推送消息。实现步骤1). 直接使用websocket.html页面作为WebSocket客户端2). 导入WebSocket的maven坐标3). 导入WebSocket服务端组件WebSocketServer用于和客户端通信4). 导入配置类WebSocketConfiguration注册WebSocket的服务端组件5). 导入定时任务类WebSocketTask定时向客户端推送数据
1). 定义websocket.html页面!DOCTYPE HTML
html
headmeta charsetUTF-8titleWebSocket Demo/title
/head
bodyinput idtext typetext /button onclicksend()发送消息/buttonbutton onclickcloseWebSocket()关闭连接/buttondiv idmessage/div
/body
script typetext/javascriptfunction abc() {//code}var websocket null;//Math.random()返回一个0包含到 1不包含的浮点数//toString(32): 转成字符串//substr(): 从指定位置开始截取字符串var clientId Math.random().toString(36).substr(2);//判断当前浏览器是否支持WebSocketif(WebSocket in window){//连接WebSocket节点websocket new WebSocket(ws://localhost:8080/ws/clientId);}else{alert(Not support websocket)}//连接发生错误的回调方法websocket.onerror function(){setMessageInnerHTML(error);};//连接成功建立的回调方法websocket.onopen function(){setMessageInnerHTML(连接成功);}//接收到消息的回调方法websocket.onmessage function(event){setMessageInnerHTML(event.data); //event.data是服务器返回的数据}//连接关闭的回调方法websocket.onclose function(){setMessageInnerHTML(close);}//监听窗口关闭事件//当窗口关闭时主动去关闭websocket连接防止连接还没断开就关闭窗口server端会抛异常。window.onbeforeunload function(){websocket.close();}//将消息显示在网页上function setMessageInnerHTML(innerHTML){document.getElementById(message).innerHTML innerHTML br/;}//发送消息function send(){var message document.getElementById(text).value;websocket.send(message);}//关闭连接function closeWebSocket() {websocket.close();}
/script
/html2). 导入maven坐标dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-websocket/artifactId
/dependency3). 定义WebSocket服务端组件/*** WebSocket服务*/
Component
ServerEndpoint(/ws/{sid})
public class WebSocketServer {//存放会话对象private static MapString, Session sessionMap new HashMap();/*** 连接建立成功调用的方法*/OnOpenpublic void onOpen(Session session, PathParam(sid) String sid) {System.out.println(客户端 sid 建立连接);sessionMap.put(sid, session);}/*** 收到客户端消息后调用的方法** param message 客户端发送过来的消息*/OnMessagepublic void onMessage(String message, PathParam(sid) String sid) {System.out.println(收到来自客户端 sid 的信息: message);}/*** 连接关闭调用的方法** param sid*/OnClosepublic void onClose(PathParam(sid) String sid) {System.out.println(连接断开: sid);sessionMap.remove(sid);}/*** 群发** param message*/public void sendToAllClient(String message) {CollectionSession sessions sessionMap.values();for (Session session : sessions) {try {//服务器向客户端发送消息session.getBasicRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}}} 4). 定义配置类注册WebSocket的服务端组件/*** WebSocket配置类用于注册WebSocket的Bean*/
Configuration
public class WebSocketConfiguration {Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}5). 定义定时任务类定时向客户端推送数据Component
public class WebSocketTask {Autowiredprivate WebSocketServer webSocketServer;/*** 通过WebSocket每隔5秒向客户端发送消息*/Scheduled(cron 0/5 * * * * ?)public void sendMessageToClient() {webSocketServer.sendToAllClient(这是来自服务端的消息 LocalTime.now());}
}5、Apache ECharts Apache ECharts 是一款基于 Javascript 的数据可视化图表库提供直观生动可交互可个性化定制的数据可视化图表。- 官网地址https://echarts.apache.org/zh/index.html常见效果展示
1). 柱状图-bar
2). 饼图-pie
3). 折线图-line- 总结不管是哪种形式的图形最本质的东西实际上是数据它其实是对数据的一种可视化展示。- Apache Echarts官方提供的快速入门https://echarts.apache.org/handbook/zh/get-started/实现步骤1). 引入echarts.js 文件2). 为 ECharts 准备一个设置宽高的 DOM3). 初始化echarts实例4). 指定图表的配置项和数据5). 使用指定的配置项和数据显示图表代码开发!DOCTYPE html
htmlheadmeta charsetutf-8 /titleECharts/title!-- 引入刚刚下载的 ECharts 文件 --script srcecharts.js/script/headbody!-- 为 ECharts 准备一个定义了宽高的 DOM --div idmain stylewidth: 600px;height:400px;/divscript typetext/javascript// 基于准备好的dom初始化echarts实例var myChart echarts.init(document.getElementById(main));// 指定图表的配置项和数据var option {title: {text: ECharts 入门示例},tooltip: {},legend: {data: [销量]},xAxis: {data: [衬衫, 羊毛衫, 雪纺衫, 裤子, 高跟鞋, 袜子]},yAxis: {},series: [{name: 销量,type: bar,data: [5, 20, 36, 10, 10, 20]}]};// 使用刚指定的配置项和数据显示图表。myChart.setOption(option);/script/body
/html- 总结使用Echarts重点在于研究当前图表所需的数据格式。通常是需要后端提供符合格式要求的动态数据然后响应给前端来展示图表。6、Apache POI Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目主要任务是创建和维护Java API以基于Office Open XML标准OOXML和Microsoft的OLE 2复合文档格式OLE2处理各种文件格式 该API的组件列表如下API作用POIFS该组件是所有其他POI元素的基本因素。 它用于显式读取不同的文件。HSSF用于读取和写入MS-Excel文件的xls格式。XSSF用于MS-Excel的xlsx文件格式。SXSSF用于比较大的xlsx文件格式。HWPF用于读写MS-Word的doc扩展文件。XWPF用于读写MS-Word的docx扩展文件。HSLF用于阅读创建和编辑PowerPoint演示文稿。HDGF它包含MS-Visio二进制文件的类和方法。HPBF用于读写MS-Publisher文件。 简单来说就是我们可以使用 POI 在 Java 中对Miscrosoft Office各种文件进行读写操作。一般情况下POI 都是用于操作 Excel 文件。**Apache POI 的应用场景**- 银行网银系统导出交易明细- 各种业务系统导出Excel报表- 批量导入业务数据** 入门案例 **
Apache POI既可以将数据写入Excel文件也可以读取Excel文件中的数据。Apache POI的maven坐标dependencygroupIdorg.apache.poi/groupIdartifactIdpoi/artifactId
/dependency
dependencygroupIdorg.apache.poi/groupIdartifactIdpoi-ooxml/artifactId
/dependency1、将数据写入Excel文件public class POITest {/*** 基于POI向Excel文件写入数据* throws Exception*/public static void write() throws Exception{//在内存中创建一个Excel文件对象XSSFWorkbook excel new XSSFWorkbook();//创建Sheet页XSSFSheet sheet excel.createSheet(itcast);//在Sheet页中创建行0表示第1行XSSFRow row1 sheet.createRow(0);//创建单元格并在单元格中设置值单元格编号也是从0开始1表示第2个单元格row1.createCell(1).setCellValue(姓名);row1.createCell(2).setCellValue(城市);XSSFRow row2 sheet.createRow(1);row2.createCell(1).setCellValue(张三);row2.createCell(2).setCellValue(北京);XSSFRow row3 sheet.createRow(2);row3.createCell(1).setCellValue(李四);row3.createCell(2).setCellValue(上海);FileOutputStream out new FileOutputStream(new File(D:\\itcast.xlsx));//通过输出流将内存中的Excel文件写入到磁盘上excel.write(out);//关闭资源out.flush();out.close();excel.close();}public static void main(String[] args) throws Exception {write();}
}4).设置表头加粗【扩展】//给表头字体加粗 - 单元格样式
XSSFCellStyle cellStyle workbook.createCellStyle();//产生一个单元格样式对象
XSSFFont font workbook.createFont(); //产生一个字体对象
font.setBold(true); //加粗
cellStyle.setFont(font);XSSFCell cell1 row1.createCell(1);
cell1.setCellValue(姓名);
cell1.setCellStyle(cellStyle); //给当前单元格设置一个样式XSSFCell cell2 row1.createCell(2);
cell2.setCellValue(城市);
cell2.setCellStyle(cellStyle); //给当前单元格设置一个样式2、读取Excel文件中的数据1). 代码开发/*** 基于POI读取Excel文件* throws Exception*/public static void read() throws Exception{FileInputStream in new FileInputStream(new File(D:\\itcast.xlsx));//通过输入流读取指定的Excel文件XSSFWorkbook excel new XSSFWorkbook(in);//获取Excel文件的第1个Sheet页XSSFSheet sheet excel.getSheetAt(0);//获取Sheet页中的最后一行的行号int lastRowNum sheet.getLastRowNum();for (int i 0; i lastRowNum; i) {//获取Sheet页中的行XSSFRow titleRow sheet.getRow(i);//获取行的第2个单元格XSSFCell cell1 titleRow.getCell(1);//获取单元格中的文本内容String cellValue1 cell1.getStringCellValue();//获取行的第3个单元格XSSFCell cell2 titleRow.getCell(2);//获取单元格中的文本内容String cellValue2 cell2.getStringCellValue();System.out.println(cellValue1 cellValue2);}//关闭资源in.close();excel.close();}public static void main(String[] args) throws Exception {read();}7、导出Excel报表
在ReportServiceImpl实现类中实现导出运营数据报表的方法:1、将资料中的运营数据报表模板.xlsx拷贝到项目的resources/template目录中
2、停止项目删除target目录Autowired
private WorkspaceService workspaceService;/*** 导出近30天的运营数据报表** param response**/
public void exportBusinessData(HttpServletResponse response) {LocalDate begin LocalDate.now().minusDays(30);LocalDate end LocalDate.now().minusDays(1);//查询概览运营数据提供给Excel模板文件BusinessDataVO businessData workspaceService.getBusinessData(LocalDateTime.of(begin, LocalTime.MIN),LocalDateTime.of(end, LocalTime.MAX));//需要从当前运行路径下获取excel模版文件InputStream inputStream this.getClass().getClassLoader().getResourceAsStream(template/运营数据报表模板.xlsx);try {//基于提供好的模板文件创建一个新的Excel表格对象XSSFWorkbook excel new XSSFWorkbook(inputStream);//获得Excel文件中的一个Sheet页XSSFSheet sheet excel.getSheet(Sheet1);//在第2行第2列设置统计日期2026-05-01至2026-05-30sheet.getRow(1).getCell(1).setCellValue(begin 至 end);//获得第4行XSSFRow row sheet.getRow(3);//在第3列设置营业额row.getCell(2).setCellValue(businessData.getTurnover());//在第5列设置订单完成率row.getCell(4).setCellValue(businessData.getOrderCompletionRate());//在第7列设置新增用户数row.getCell(6).setCellValue(businessData.getNewUsers());//获取第5行row sheet.getRow(4);//在第3列设置有效订单row.getCell(2).setCellValue(businessData.getValidOrderCount());//在第5列设置平均客单价row.getCell(4).setCellValue(businessData.getUnitPrice());//写入明细数据按天显示for (int i 0; i 30; i) {LocalDate date begin.plusDays(i);//准备订单明细数据businessData workspaceService.getBusinessData(LocalDateTime.of(date, LocalTime.MIN),LocalDateTime.of(date, LocalTime.MAX));//从第8行开始写入row sheet.getRow(7 i);row.getCell(1).setCellValue(date.toString());row.getCell(2).setCellValue(businessData.getTurnover());row.getCell(3).setCellValue(businessData.getValidOrderCount());row.getCell(4).setCellValue(businessData.getOrderCompletionRate());row.getCell(5).setCellValue(businessData.getUnitPrice());row.getCell(6).setCellValue(businessData.getNewUsers());}//通过输出流将文件下载到客户端浏览器中ServletOutputStream out response.getOutputStream();excel.write(out);//关闭资源out.flush();out.close();excel.close();} catch (IOException e) {e.printStackTrace();}
}