【笔记/后端】谷粒商城基础篇

article/2025/9/13 22:06:56

目录

  • 一、环境配置
    • 1 Docker
      • 1.1 Docker是什么?
      • 1.2 安装&启动
        • 1.2.1 阿里云镜像加速
      • 1.3 安装MySQL
      • 1.4 安装Redis
    • 2 开发环境
      • 2.1 Maven
      • 2.2 Git
      • 2.3 Node
  • 二、创建微服务项目
    • 1 内容
    • 2 问题记录
    • 3 renren-generator
  • 三、分布式组件
    • 1 Nacos
      • 1.1 注册中心
      • 1.2 配置中心
        • 1.2.1 如何使用Nacos作为配置中心统一管理配置?
      • 1.2.2 细节
        • 1.2.3 同时加载多个配置集
    • 2 Feign
      • 1.1 使用场景
      • 1.2 调用其他服务的Application
    • 3 Gateway
    • 4 Aliyun OSS
  • 四、前端开发基础
    • 1 ES6
      • 1.1 let和var
      • 1.2 解构表达式
      • 1.3 字符串扩展
      • 1.4 函数优化
      • 1.5 对象优化
      • 1.6 对象扩展运算符
      • 1.7 map和reduce
    • 1.8 promise
      • 1.9 模块化
        • 1.9.1 导入导出
        • 1.9.2 简化导出
        • 1.9.3 不命名导出
    • 2 Vue
      • 2.1 基础属性
      • 2.2 计算属性,监听器,过滤器
      • 2.3 组件化
  • 五、业务处理
    • 1 逻辑删除
    • 2 JSR303校验
      • 2.1 开启校验
      • 2.2 使用ControllerAdvice来集中处理异常
      • 2.3 分组校验
      • 2.4 自定义校验
    • 3 POJO和SKU
      • 3.1 电商领域名词
      • 3.2 Object分类
    • 4 其他
  • 六、总结
    • 1 分布式基础概念
    • 2 基础开发
    • 3 环境
    • 4 开发规范

一、环境配置

1 Docker

1.1 Docker是什么?

基于镜像启动各种容器,每一种容器都是一个完整的运行环境,容器之间互相隔离。

1.2 安装&启动

docker官网:Developers -> Docs -> Get Docker -> Docker For Linux

systemctl start docker

docker images查看安装的全部镜像

systemctl enable docker开机自启

1.2.1 阿里云镜像加速

阿里云官网 -> 登录 -> 控制台 -> 容器镜像服务 -> 镜像工具 -> 镜像加速器 -> CentOS

1.3 安装MySQL

docker pull mysql:5.7

docker run -p 3306:3306 --name mysql \\ # 主机3306映射到容器3306 --name起名字
-v /mydata/mysql/log:/var/log/mysql \\ # 容器内文件挂载到虚拟机中
-v /mydata/mysql/data:/var/lib/mysql \\
-v /mydata/mysql/conf:/etc/mysql \\
-e MYSQL_ROOT_PASSWORD=root \\ # root用户密码
-d mysql:5.7 # 后台运行

下面供复制使用:

docker run -p 3306:3306 --name mysql -v /mydata/mysql/log:/var/log/mysql -v /mydata/mysql/data:/var/lib/mysql -v /mydata/mysql/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7

docker ps 查看现在运行的全部镜像
docker exec -it mysql /bin/bash 进入到容器内部
whereis mysql 在容器内部执行来查看mysql位置

修改/mydata/mysql/conf下mysql配置

[client]
default-character-set=utf8[mysql]
default-character-set=utf8[mysqld]
init_connect='SET collation_connection = utf8_unicode_ci'
init_connect='SET NAMES utf8'
character-set-server=utf8
collation-server=utf8_unicode_ci
skip-character-set-client-handshake
skip-name-resolve

docker restart mysql

1.4 安装Redis

docker pull redis 不写版本下载最新版本

docker run -p 6379:6379 --name redis -v /mydata/redis/data:/data \
-v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf

在/mydata/redis/conf/redis.conf中添加

appendonly yes

开启持久化

虚拟机重启后自动启动镜像

docker update redis --restart=always

2 开发环境

2.1 Maven

问题1 依赖子模块的模块打包失败

解决 给上层(父)模块install后成功

2.2 Git

配置git

# 配置用户名
git config --global user.name "username" # 名字
# 配置邮箱
git config --global user.email "username@email.com" # 注册账号时使用的邮箱

在码云中进入设置 -> SSH公钥 -> 将本机公钥粘贴进去实现后续免密提交

2.3 Node

npm配置镜像仓库:
npm config set register <http://registry.npm.taobao.org/>

install后使用npm run dev运行

问题: npm安装失败。

解决 1. node使用10.X版本,python版本改3.7(非必要) 2. 网络问题 3. npm install遇到循环c++提示等待即可


二、创建微服务项目

1 内容

商品服务、仓储服务、订单服务、优惠券服务、用户服务

共同:

  1. web openfeign
  2. 每一个服务,包名 com.atguigu.gulimall.xxx(product/order/ware/coupon/member)
  3. 模块名:gulimall-coupon

2 问题记录

问题1 报错 POM文件爆红,以及<repository>中的网址禁止访问
解决 Maven版本问题,3.6.3解决

问题2 报错如下

'parent.relativePath' of POM io.renren:renren-fast:3.0.0 (/Users/dujianzhang/Documents/IdeaProjects/gulimall/renren-fast/pom.xml) points at pers.djz.gulimall:gulimall instead of org.springframework.boot:spring-boot-starter-parent, please verify your project structure```

解决 子模块没有指向上级目录父模块,在<parent>标签中添加<relativePath/>使Maven打包时每次都直接从仓库获取

问题3 报错如下

The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)at java.lang.reflect.Constructor.newInstance(Constructor.java:423)at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:61)

解决 尝试与MySQL建立SSL连接失败,在URL后面加上?useSSL=false

3 renren-generator

使用方法

  1. 修改application.yml中的dataSource相关信息
  2. 修改generator.properties中生成项目目录结构信息
  3. 运行Application,在浏览器中前往生成器页面
  4. 勾选要进行逆向工程的数据库表,点击生成代码开始下载

问题1 idea不显示yaml小树叶

解决 备份编辑器设置,恢复编译器默认设置,重新导入备份设置不恢复file相关设置

问题2 Longblob无法解析

解决 替换成byte类型

问题3 renren generator实体类备注乱码

解决 使用navicat执行sql不要导入sql文件,复制sql文本执行查询


三、分布式组件

在这里插入图片描述

1 Nacos

1.1 注册中心

启动方式: ./startup.sh -m standalone 在application.yml中配置nacos注册中心的信息:

spring:    cloud:    nacos:            discovery:            server-addr: localhost:8848#一定要有应用名    application:    name: gulimall-coupon

问题1 nacos在macOS上找不到/Library/InternetPermission denied

解决 mac自带了一个jdk,在环境变量中声明自己安装的jdk的JAVA_HOME

export JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_311.jdk/Contents/Home"
export PATH="$PATH:$JAVA_HOME/bin"
export CLASSPATH="$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:."

问题2 macOS上7000端口占用问题

解决 7000端口用于AirPlay,使用7001

1.2 配置中心

1.2.1 如何使用Nacos作为配置中心统一管理配置?

  1. 引入依赖
<dependency>    <groupId>com.alibaba.cloud</groupId>    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
  1. 创建一个bootstrap.properties
spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=localhost:8848
  1. 在配置中心添加一个默认的数据集(Data Id) gulimall-coupon.properties,默认规则:应用名.properties
  2. 给应用名.properties添加任何配置
  3. 动态获取配置
    @RefreshScope动态获取并刷新配置,注解在Application类上
    @Value("${配置项的名}")获取到配置
    如果配置中心和当前应用的配置文件中都配置了相同的项,优先使用配置中心的配置

1.2.2 细节

  1. 命名空间:配置隔离
    默认:public(保留空间),默认新增的所有配置都在public空间
    1. 开发、测试、生产,利用命名空间来做环境隔离
      注意:在bootstrap.properties配置上,需要使用哪个命名空间下的配置
      spring.cloud.nacos.config.namespace=aa34a377-e984-4082-8959-e41e35dd9d51
    2. 每一个微服务之间互相隔离配置,每一个微服务都创建自己的命名空间,只加载自己命名空间下的所有配置
  2. 配置集:所有的配置的集合
  3. 配置集ID:类似文件名
    Data ID:类似文件名
  4. 配置分组
    默认所有的配置集都属于DEFAULT_GROUP
    spring.cloud.nacos.config.group=1111

每个微服务创建自己的命名空间,使用配置分组区分环境,dev, test, prod

1.2.3 同时加载多个配置集

spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=dev
spring.cloud.nacos.config.ext-config[0].refresh=true

yaml写法

spring:cloud:nacos:ext-config:- data-id: oss.ymlgroup: DEFAULT_GROUPrefresh: true- data-id: ...group: ...refresh: ...
  1. 微服务任何配置信息,任何配置文件都可以放在配置中心中
  2. 只需要在bootstrap.properties中说明加载配置中心中哪些配置文件即可
  3. @Value @ConfigurationProperties以前SpringBoot任何方法从配置文件中获取值都能使用,配置中心有的优先使用配置中心的

问题1 配置了bootstrap.properties中的nacos config服务器地址,依然报错endpoint is blank

解决 没有读取配置文件,springboot 2.4以上要在pom中添加依赖:

<dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-starter-bootstrap</artifactId>    <version>3.1.0</version>
</dependency>

或者是没有在resources下创建bootstrap配置文件指明配置中心地址

2 Feign

1.1 使用场景

  1. member想要调用coupon中的信息
  2. member向nacos注册中心查询目标服务地址
  3. 查询

1.2 调用其他服务的Application

/** 想要远程调用别的服务* 1 引入openfeign* 2 编写一个接口,告诉SpringCloud这个接口需要调用远程服务*   2.1 声明接口的每一个方法都是调用哪个远程服务的哪个请求* 3 开启远程调用功能* */
@EnableFeignClients(basePackages = "pers.djz.gulimall.member.feign")
@EnableDiscoveryClient
@SpringBootApplicationpublic 
class GulimallMemberApplication {    public static void main(String[] args) {        SpringApplication.run(GulimallMemberApplication.class, args);    }
}

feign包下的接口:

// name是nacos注册中心中要访问的微服务名称,url可不写
@FeignClient(name = "gulimall-coupon", url = "http://localhost:7001/")
public interface CouponFeignService {    // 方法名不必相同,但访问url要对应上@PostMapping("/coupon/skufullreduction/saveInfo")R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);
}
  1. @RequestBody将这个对象转为json
  2. 找到gulimall-coupon服务,给/coupon/spubounds/save发送请求。将上一步转的json放在请求体位置,发送请求
  3. 对方服务收到请求。请求体里有JSON数据,@RequestBody SpuBoundsEntity spuBounds将请求体的JSON转为SpuBoundsEntity

只要JSON数据模型是兼容的,双方服务无需使用同一个VO

问题 运行后feign报错ribbon和loadbalance问题
解决 pom中如下引用

<dependency><groupId>com.octopus.gulimall</groupId><artifactId>common</artifactId><version>0.0.1-SNAPSHOT</version><exclusions><exclusion><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-ribbon</artifactId></exclusion></exclusions>
</dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

3 Gateway

在这里插入图片描述

spring:cloud:gateway:routes:- id: qq_route# uri是符合断言的请求会跳转到的地址uri: https://www.qq.com/predicates:- Query=url, qq- id: baidu_routeuri: https://www.weibo.com/predicates:- Query=url, weibo

问题1 启动网关application报错:

Description:Parameter 0 of method loadBalancerWebClientBuilderBeanPostProcessor in org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration required a bean of type 'org.springframework.cloud.client.loadbalancer.reactive.DeferringLoadBalancerExchangeFilterFunction' that could not be found.Action:Consider defining a bean of type 'org.springframework.cloud.client.loadbalancer.reactive.DeferringLoadBalancerExchangeFilterFunction' in your configuration.Process finished with exit code 1

解决 添加依赖:

<dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-starter-loadbalancer</artifactId>    <version>3.1.1</version>
</dependency>

问题2 通过lb://renren-fast进行网关控制报503错误,提示No servers available for service: renren-fast
解决 SpringCloud Alibaba和SpringBoot版本问题,改为2.1.8.RELEASEGreenwich.SR3

4 Aliyun OSS

  • 在Aliyun官网使用对象存储服务,创建一个新的bucket存储文件
    • 使用公共读
  • 右上角AccessKey创建子账户供Open API调用
  • Java导入starter后,自动注入OssClient类对象(新版本使用OSS类)
    • starter导入后在application.yaml中配置endpoint、access-key和secret-key
  • 实际开发中客户端向服务器请求签名后直接带着签名上传到OSS服务器(使用方法参考文档)
spring:cloud:alicloud:access-key: xxxsecret-key: xxxoss:endpoint: oss-cn-beijing.aliyuncs.combucket: xxx
@RestController
@RequestMapping("/oss")
public class OssController {@Autowiredprivate OSS ossClient;@Value("${spring.cloud.alicloud.oss.endpoint}")private String endpoint;@Value("${spring.cloud.alicloud.oss.bucket}")private String bucket;@Value("${spring.cloud.alicloud.access-key}")private String accessId;@RequestMapping("/policy")public R policy() {// 填写Host地址,格式为https://bucketname.endpoint。String host = "https://" + bucket + "." + endpoint;// 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
//        String callbackUrl = "https://192.168.0.0:8888";// 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());String dir = format + "/";Map<String, String> respMap = null;try {long expireTime = 30;long expireEndTime = System.currentTimeMillis() + expireTime * 1000;Date expiration = new Date(expireEndTime);PolicyConditions policyConds = new PolicyConditions();policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);byte[] binaryData = postPolicy.getBytes("utf-8");String encodedPolicy = BinaryUtil.toBase64String(binaryData);String postSignature = ossClient.calculatePostSignature(postPolicy);respMap = new LinkedHashMap<String, String>();respMap.put("accessid", accessId);respMap.put("policy", encodedPolicy);respMap.put("signature", postSignature);respMap.put("dir", dir);respMap.put("host", host);respMap.put("expire", String.valueOf(expireEndTime / 1000));// respMap.put("expire", formatISO8601Date(expiration));} catch (Exception e) {// Assert.fail(e.getMessage());System.out.println(e.getMessage());}return R.ok().put("data", respMap);}
}

四、前端开发基础

1 ES6

ECMAScript是浏览器脚本语言的规范

1.1 let和var

  1. let有严格的作用域,var会越域
  2. let不能重复声明同名变量,var可以
  3. let不存在变量提升,var会 变量提升:声明会提升到作用域顶端
function fun() {a = 1;var a;
}// 实际执行 var a; -> a = 1;

1.2 解构表达式

const [a, b, c] = [1, 2, 3];// a = 1, b = 2, c = 3
let person = {name: "jack", age: 18};
const {name, age} = person;// name = "jack", age = "age"

1.3 字符串扩展

let str = "hello.vue";
str.startsWith("hello"); 
// truestr.endsWith("vue"); 
// truestr.includes("e"); // true
// 字符串模版(多行字符串)
let ss = `<div>    <span>hello</span></div>`
// 插入表达式(用``)
let info = `我是${abc}, 今年${age}了, 我想说: ${fun()}`;

1.4 函数优化

b = b || 1; // 判断b是否为空,如空给默认值1
function fun(a, b=1) {    ...}
// 不定参数
function fun(...values) {console.log(values.length)
}
// 箭头函数
var print = obj => console.log(obj); 
// print是一个函数
var sum = (a, b) => return a + b;
var sum2 = (a, b) => {let c = a + b;    return a + c;
}
// 箭头函数+解构
var fun = ({name}) => console.log(name);
fun(person);

1.5 对象优化

const person = {name: "jack", age: 18};
Object.keys(person);
Object.values(person);
Object.entries(person);
// 合并
const target = {a: 1};
const source1 = {b: 2};
const source2 = {c: 3};
Object.assign(target, source1, source2);
// 声明对象简写
const age = 23;
const name = "张三";
const person = {age, name}; 
// 变量和属性同名可以简写
// 对象的函数属性简写
let person = {name: "jack",eat: function (food) {console.log(this.name + "在吃" + food);},// 箭头函数this不能使用,使用对象.属性来获取eat2: food => console.log(this.name + "在吃" + food);
}
person.eat("banana");

1.6 对象扩展运算符

// 拷贝对象
let p1 = {name: "jack", age: 18};
let someone = { ...p1 }
// 合并对象
let age1 = {age: 15};
let name1 = {name: "Amy"};
let p2 = {name: "zhangsan"};
p2 = {...age1, ...name1}; // p2.name = "Amy"

1.7 map和reduce

let arr = ['1', '20', '-5'];
arr = arr.map((item) => {return item * 2;
});
arr = arr.map(item => item * 2);
/**
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
1、previousValue(上一次调用回调返回的值,或者是提供的初始值(initialvalue))
2、currentvalue (数组中当前被处理的元素)
3、index(当前元素在数组中的索引)
4、array(调用 reduce 的数组)
*/
let result = arr.reduce((a, b) => {console.log(a, b);return a + b;
}, 100); // 100 1 ...

1.8 promise

Promise可以封装异步操作

function get(url, data) {return new Promise((resolve, reject) => {$.ajax({url: url,data: data,success: function (data) {resolve(data);},error: function (err) {reject(err);}});});
}
// 多级分类查询
get("mock/user.json").then((data) => {console.log("用户查询成功: ", data);return get(`mock/user_course_${data.id}.json`);
}).then((data) => {console.log("课程查询成功: ", data);return get(`mock/course_score_${data.id}.json`);
}).then((data) => {console.log("课程成绩查询成功", data);
}).catch((err) => {console.log("出现异常", err);
});

1.9 模块化

1.9.1 导入导出

util.js

var name = "jack";var age = 21;
function add(a, b) {return a + b;
}
export {name, age, add}

main.js

import {name, age, add} from "./util.js"

1.9.2 简化导出

export const util = ...

1.9.3 不命名导出

xx.js

export default {sum(a, b) {return a + b;}
}
// 随意命名
import abc from "./xx.js"

2 Vue

问题1 eslint语法检查过于严格
解决 在.eslintignore文件中添加要忽略的内容,如*.vue

<body><div id="app"><input type="text" v-model="num"/><button v-on:click="num++">👍</button><button v-on:click="cancel">👎</button><h1>hello {{ name }}! 有{{ num }}个人为你点赞</h1></div><script src="./node_modules/vue/dist/vue.js"></script><script>// 1 声明式渲染let vm = new Vue({el: "#app", // 绑定元素data: { // 封装数据name: "zhangsan",num: 1},methods: { // 封装方法cancel() {this.num--;}}})// 2 双向绑定,模型变化,视图变化。反之亦然// 3 事件处理// v-xx指令// 1 创建vue实例,关联页面的模版,将我们的数据data渲染到关联的模版,响应式的// 2 指令来简化对dom的一些操作// 3 声明方法来做更复杂的操作</script>
</body>

2.1 基础属性

  1. v-text v-html
  2. v-bind:xxx单向绑定(数据修改视图) e.g. v-bind:href == :href 对于class、style:
<span v-bind:class="{active:isActive,'text-danger':hasError}" v-bind:style="{color: color1;'font-size': size;}">你好</span>
<!--连字符不合法,使用''或者驼峰命名如fontSize-->
<script>...data:{isActive:true,hasError:true,color1:'red',size:'36px'}
</script>
  1. v-model=双向绑定
  2. v-on绑定事件 v-on:click= == @click

事件修饰符 阻止事件向上冒泡@click.stop 阻止默认行为@click.prevent @click.prevent.stop @click.once @keyup.down键盘↓ 5. v-for 遍历的时候加上:key来区分不同数据(要求key不重复),提高vue渲染效率

<li v-for="(user,index) in users" :key="user.name">{{ index }} -> {{ user }}<span v-for="(k,v,i) in user">{{ k }} -> {{ v }}</span>
<li>
  1. v-ifv-show
  • v-if="false"直接删除dom元素
  • v-show="false"使用display: none

还可以与v-if搭配v-else-ifv-else

示例:

<button @click="isShow = !isShow">button</button>
<span v-if="isShow">span</span>

v-for搭配:

<li v-for="(user,index) in users" :key="user.name" v-if="user.gender == ''">

2.2 计算属性,监听器,过滤器

  • computed的函数在使用到的(data属性中的)变量发生改变后再次执行进行更新
  • watch可以让我们监控一个值的变化,从而做出相应的反应
<li>总价:{{ totalPrice }}</li>
<li v-for="user in users">{{user.gender == 1 ? "男" : "女"}} 等同于过滤器 {{user.gender | genderFilter}}
</li>
<script>new Vue({el: "#app",data: {price: 20.00,num: 10},computed: {totalPrice() {return this.price * num;}},watch: {num: function(oldVal, newVal) {if (newVal > 3) {alert("超过3了");}}},filters: {genderFilter(val) {if (val == 1) {return "男";} else {return "女";}}}});
</script>

全局过滤器

Vue.filter("gFilter", function() {    ...})

使用:

{{ user.gender | gFilter }}

2.3 组件化

<counter></counter>
<div id="app"><button-counter></button-counter>
</div>
<script>// 1. 全局声明注册一个组件Vue.component("counter", {template: `<button @click="count++">我被点击了 {{count}} 次</button>`,data() {return {count: 1}}});// 2. 局部声明一个组件const buttonCounter = {template: `<button @click="count++">我被点击了 {{count}} 次</button>`,data() {return {count: 1}}};new Vue({el: "#app",data: {count: 1},components: {'button-counter': buttonCounter}});
</script>

五、业务处理

1 逻辑删除

一般删除记录通过设置标志位进行逻辑删除,而不是真正的物理删除

使用MybatisPlus实现逻辑删除的步骤

  1. 配置全局逻辑删除规则(可省略)
mybatis-plus:global-config:db-config:logic-delete-value: 1logic-not-delete-value: 0
  1. 配置逻辑删除的组件Bean(过时)
  2. 给Bean的标志位属性加上逻辑删除注解 @TableLogic

数据库中对应字段值不能为空才能进行逻辑删除,否则删除后标志位仍然为空
实际执行:
UPDATE pms_category SET show_status=0 WHERE cat_id IN ( ? ) AND show_status=1

@TableLogic(value = "1", delval = "0")
private Integer showStatus;

2 JSR303校验

2.1 开启校验

  1. 给Bean的属性上方添加校验注解:
    javax.validation.constraints
    @NotNull
    @NotBlank(message = "自定义提示信息")
  2. 开启校验功能,在controller方法参数中的要校验的Bean前添加 @Valid
    效果:校验错误以后会有默认的响应
  3. 给校验的参数Bean后紧跟一个BindingResult,就可以获取到校验的结果
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result) {if (result.hasErrors()) {Map<String, String> map = new HashMap<>();result.getFieldErrors().forEach((item) -> {// FieldError获取到错误提示String message = item.getDefaultMessage();// 获取错误的属性的名字String field = item.getField();map.put(field, message);});return R.error(400, "提交的数据不合法").put("data", map);}brandService.save(brand);return R.ok();
}

常用注解
@Null 限制只能为null
@NotNull 限制必须不为null
@AssertFalse 限制必须为false
@AssertTrue 限制必须为true
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future 限制必须是一个将来的日期
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Past 限制必须是一个过去的日期
@Pattern(value) 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间
@Past 验证注解的元素值(日期类型)比当前时间早
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

2.2 使用ControllerAdvice来集中处理异常

@Slf4j
@RestControllerAdvice(basePackages = "com.octopus.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {@ExceptionHandler(MethodArgumentNotValidException.class)public R handleValidException(MethodArgumentNotValidException e) {log.error("数据校验出现问题{}, 异常类型:{}", e.getMessage(), e.getClass());BindingResult bindingResult = e.getBindingResult();Map<String, String> errorMap = new HashMap<>();bindingResult.getFieldErrors().forEach(fieldError -> {errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());});return R.error(400, "数据校验出现问题").put("data", errorMap);}
}

2.3 分组校验

@NotBlank(message = "品牌名不能为空", groups = {AddGroup.class, UpdateGroup.class})
public R save(@Validated(AddGroup.class) @RequestBody BrandEntity brand/* , BindingResult result */)

默认没有指定分组的校验注解@NotBlank,在分组校验情况下不生效,只会在@Validated下生效

2.4 自定义校验

编写一个自定义的校验注解 - 编写一个自定义的校验, 实现ConstraintValidator的两个方法(初始化和校验方法),并添加到注解.java文件上面validatedBy

private Set<Integer> set = new HashSet<>();
// 初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {int[] vals = constraintAnnotation.vals();for (int val : vals) {set.add(val);}}
/**** @param value 需要校验的值* @param constraintValidatorContext* @return*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {return set.contains(value);
}
  • 关联自定义的校验器和自定义的校验注解
  • 默认message可以读取类路径下ValidationMessages.properties中的内容
@Documented
// 可以指定多个不同的校验器,适配不同类型的校验,取决于父类ConstraintValidator的第二个范型
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {String message() default "{com.octopus.common.valid.ListValue.message}";Class<?>[] groups() default { };Class<? extends Payload>[] payload() default { };int[] vals();
}

问题1 @Pattern正则正确匹配也报错

解决 regexp不要加前后/

问题2 高版本SpringBoot(2.3之后)没有效果
解决 引入依赖

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

问题3 校验不通过后返回的响应为503且不含错误原因
解决 在yaml中配置如下,这样返回400错误并包含错误原因

server:port: 10000error:include-binding-errors: always

问题4 ValidationMessages.properties 配置文件中的中文错误信息导致乱码问题
解决 添加配置类

@Configuration
public class ValidationConfig {@Beanpublic Validator validator() {LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();factoryBean.setMessageInterpolator(interpolatorFactory.getObject());ResourceBundleMessageSource source = new ResourceBundleMessageSource();source.setDefaultEncoding("utf-8");source.setCacheMillis(-1);// 文件名前缀source.setBasename("ValidationMessages");factoryBean.setValidationMessageSource(source);return factoryBean;}
}

3 POJO和SKU

3.1 电商领域名词

  • SPU(Standard Product Unit 标准化产品单元):iPhone 13 决定规格参数
  • SKU(Stock Keeping Unit 库存量单位):iPhone 13 512GB 白色 决定销售属性

用面向对象作类比,SPU是类,SKU是对象

3.2 Object分类

PO(persist object) 持久对象对应数据库中的一条记录
DO(Domain Object) 领域对象从现实世界抽象出来的有形或无形的业务实体
TO(Transfer Object) 数据传输对象不同应用程序之间传输的对象
DTO(Data Transfer Object) 数据传输对象泛指展示层与服务层之间的数据传输对象
VO(value object) 值对象View Object,视图对象;接受页面传递来的对象;业务处理完成的对象封装成页面要用的数据
BO(business object) 业务对象主要用作把业务逻辑包括的一个或多个其他的对象封装为一个对象
POJO(plain ordinary java object) 简单无规则java对象上述全属于POJO
DAO(data access object) 数据访问对象配合VO,提供CRUD操作

4 其他

  1. 让Entity对象在被转换为JSON格式时添加筛选条件
    @JsonInclude(JsonInclude.Include.NON_EMPTY) 为空的属性包括数组长度为0的从JSON中移除
  2. 在application.yaml中配置jackson使返回给客户端的数据中的时间被格式化:
    spring:jackson:time-zone: GMT+8date-format: yyyy-MM-dd HH:mm:ss
    

六、总结

1 分布式基础概念

  • 微服务、 汪册中心,配置中心、远程调用、Feign、网关

2 基础开发

  • SpringBoot2.0、 Springcloud、 Mybatis-Plus、vue组件化、阿里云对象存储

3 环境

  • Vagrant. Linux、Docker、 MysQL、 Redis、逆向工程&人人开源

4 开发规范

  • 数据校验JSR303、全局异常处理、全局统一返回、全局跨域处理 • 枚举状态、业务状态码、VO与TO与PO划分、逻辑删除 • Lombok: @Data @SIf4j

http://chatgpt.dhexx.cn/article/PYF6DJUr.shtml

相关文章

谷粒商城(二)

谷粒商城&#xff08;二&#xff09; 后台商品服务 - 三级分类1、查询1&#xff09;、接口编写2&#xff09;、树形展示三级分类数据3&#xff09;、配置网关路由1 更改前端 base 路径2 将服务注册进nacos3 网关模块配置路由4 测试 4&#xff09;、解决跨域 2、删除1&#xff0…

谷粒商城(五)

谷粒商城&#xff08;五&#xff09; 订单服务1、环境搭建1&#xff09;、页面2&#xff09;、代码 2、订单登录拦截3、订单确认页1&#xff09;、VO模型2&#xff09;、订单确认页数据查询1 接口编写2 调用远程服务 3&#xff09;、Feign远程调用丢失请求头启动服务报错解决 4…

谷粒商城(一)

谷粒商城&#xff08;一&#xff09; 1、环境搭建安装 dockerdocker 安装 mysqldocker 安装 redis安装配置 git准备工具 IDEA、VsCode从 gitee 初始化项目 2、创建微服务项目1&#xff09;、创建项目2&#xff09;、初始化数据库 3、使用人人开源搭建后台管理系统1&#xff09;…

谷粒商城:如何通过笔记复盘实现事半功倍?

前言 把谷粒商城做了一遍&#xff0c;其中遇的困难也记录了一下。将零散的笔记整理成有顺序的目录结构。方便自己回看、以及快速定位文章。特此记录、大部分在CSDN博客里边都可以搜索到。 大家想看的话也可以去这里看看&#xff1a;笔记地址传送门 后续还会继续维护这个笔记…

查壳、加壳、脱壳详细教程

查壳教程 1、打开软件后我们点击右上角的三个点&#xff0c;会弹出一个选择文件的窗口&#xff0c;我们选择要查壳的文件&#xff0c;同样也可以直接把需要查壳的软件拖到PEID页面里 2、这里拖入一个程序后出现如下信息页面 这里我们看到Borland Delphi 3.0,他不是一种壳&…

分布式项目-谷粒商城。

分布式项目一&#xff0c;分布图 二&#xff0c;环境搭建 1.安装linux 2.安装docker 1 卸载系统之前的docker sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine2 设…

竞业限制是什么意思?

竞业限制是指用人单位与掌握商业秘密的职工约定在劳动合同解除或终止后的一定期限内&#xff0c;劳动者不得到有竞争关系的其他用人单位任职&#xff0c;也不得自己生产与原单位有竞争关系的同类产品或经营同类业务。 竞业限制对不同的人意义是不同的&#xff0c;比如&#xf…

所谓的1261考核法算不算是末尾淘汰?

【问题】 有无精通劳动法的老哥&#xff0c;分析一下所谓的1261考核法算不算是末尾淘汰&#xff1f; 已知最后考核的那个1会被约谈&#xff0c;连续两次都是最后就会调岗或者解除劳动合同 【解析】 算的&#xff01; 应该是“末位淘汰制” 劳动法不支持“末位淘汰制”&…

数字滤波器的实现——低通滤波器再探究

在探究完滤波器原理之后&#xff0c;又面临一个问题就是数字滤波器如何实现的问题&#xff0c;因为在实际应用过程中&#xff0c;如果不接触硬件的话&#xff0c;低通滤波器一般都是通过编程实现的&#xff0c;具体代码应该怎么编写&#xff0c;在应用过程中又应该注意什么问题…

数字图像处理之低通滤波器实现原理及方法(Matlab)

1.傅里叶变换与频域 在之前的文中&#xff0c;我们已经进行过一些基本的图像处理。比如&#xff0c;使用低通滤波可以将图像模糊&#xff0c;也有些许降噪的作用。这些都是在空间域内进行的滤波处理&#xff0c;这个处理主要是依靠卷积来进行计算的。首先&#xff0c;从连续的一…

滤波器_理想低通/高通滤波器原理

1.滤波器作用 消除干扰杂讯噪声&#xff0c;对信号进行频率成分的选择2.高通滤波 过滤低频信息&#xff0c;让高频信息通过3.低通滤波 过滤高频信息&#xff0c;让低频信息通过4.理想低通滤波 D0表示通带半径&#xff0c;D(u&#xff0c;v)是到频谱中心的距离(欧式距离),公式…

带通滤波器电路图大全(三款带通滤波器电路设计原理图详解)

带通滤波器电路图设计&#xff08;一&#xff09; 传统的带通滤波器设计方法中涉及了很多复杂的理论分析和计算。针对上述缺点&#xff0c;介绍一种使用EDA软件进行带通滤波器的设计方案&#xff0c;详细阐述了使用FilterPro软件进行有源带通滤波器电路的设计步骤&#xff0c;…

T滤波器(低通滤波器)

1.电路原理 T滤波器&#xff0c;其基本原理是基于低通滤波器设计&#xff0c;实现阻高频通低频的需求&#xff0c;其电路图及传递函数如下。 2.传递函数 r(t)为输入,c(t)为输出&#xff0c;从电路原理我们得到输入输出公式&#xff1a; 将公式进行拉氏变换得到&#xff1a; 3.系…

低通滤波器和高通滤波器的程序实现原理推导

傅立叶变换,拉普拉斯变换和Z变换 对于信号分析而言,傅立叶变换是必不可少的,我们都知道傅立叶变换是把系统从时域变换到频域进行分析,那么拉普拉斯变换和Z变换是干什么的?简单的来说,由于傅里叶变换的收敛有一个狄利克雷条件&#xff0c;要求信号绝对可积/绝对可和。对于那些…

数字低通滤波器的原理及实现

首先说一下&#xff0c;数字滤波器是怎么实现的 1.首先根据电路建立低通滤波器时域系统微分方程&#xff0c;得出低通滤波器t域模型 2.其次将对时域微分方程进行拉式变换&#xff0c;得出低通滤波器的s域模型 3.将模拟滤波器转换为数字滤波器&#xff0c;对连续系统进行离散化…

简单易理解的RC滤波器(含电路仿真)

滤波器 滤波器是对波进行过滤的器件&#xff0c;是一种让某一频带内信号通过&#xff0c;同时又阻止这一频带外信号通过的电路。 滤波器主要有低通滤波器、高通滤波器和带通滤波器三种&#xff0c;按照电路工作原理又可分为无源和有源滤波器两大类。本文主要对低通、高通还有带…

一文读懂:常见低通、高通、带通三种滤波器的工作原理

滤波器 滤波器是对波进行过滤的器件&#xff0c;是一种让某一频带内信号通过&#xff0c;同时又阻止这一频带外信号通过的电路。 滤波器主要有低通滤波器、高通滤波器和带通滤波器三种&#xff0c;按照电路工作原理又可分为无源和有源滤波器两大类。今天&#xff0c;小编主要…

利用Excel对数据进行标准化处理

采用的公式为x&#xff08;x-min&#xff09;/(Max-min),这样标准化后的数据最大值为1&#xff0c;最小值为0。

数据分析-数据规范化的一些方法

数据规范化的几种方法 1. Min-Max规范化 from sklearn import preprocessing import numpy as np #初始化数据&#xff0c;每一行表示一个样本&#xff0c;每一列表示为一个特征 x np.array([[0.,-3.,1.],[3.,1.,2.],[0.,1.,-1.] ]) #将数据进行[0,1]规范化 min_max_scaler …

数据预处理(四)——数据标准化

主要内容&#xff1a; 数据预处理的必要性 数据清洗 数据集成 数据标准化 数据规约 数据变换与离散化 利用sklearn进行数据预处理 小结 四、数据标准化 不同特征之间往往具有不同的量纲&#xff0c;由此造成数值间的差异很大。因此为了消除特征之间量纲和取值范围的差异可能会造…