最近有项目指定使用Java技术的需求,给团队分享的资料
主要内容
重点
)重点
)重点
)重点
)对比
.NET Core发布计划: https://dotnet.microsoft.com/platform/support/policy/dotnet-core
Oracle Java SE Product Releases:https://www.oracle.com/java/technologies/java-se-support-roadmap.html
Web Framework Benchmarks Round 19:https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=composite
JDK:选用JDK 8,OpenJDK(Sun版本)缺少部分JDK私有内容,可能会用到一部分依赖JDK私有部分的组件,故而选用JDK 8
注
jdk:Java SE Development Kit 缩写,java标准版开发工具包
Java SE:Java Platform Standard Edition ,标准版。JavaEE 企业版、Java ME 微型版
IDE:Eclipse(免费)、IDEA(收费),注:以下示例大都使用IDEA
Springboot简介:伴随Spring而生,减少大量配置,集成常用库。大大开发降低入门难度。
结构:工程、配置、启动类、测试类。
内容:详细见代码
官网地址(https://spring.io/projects/spring-boot)
Why Spring?
Spring makes programming Java quicker, easier, and safer for everybody. Spring’s focus on speed, simplicity, and productivity has made it the world's most popular Java framework.
Spring让Java开发更快、更简单、更安全,因速度、简单、高效使得Spring成为世界上最流行的Java框架
ⅰ- ORM:MyBatis-Plus
Mybatis的扩展,简化增删查改。
官网地址(https://mp.baomidou.com)
ⅱ- 参数验证:Spring Framework Validation,Spring自带功能
Reference(https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#validation)
API Doc(https://docs.spring.io/spring-framework/docs/current/javadoc-api/)
ⅲ- Redis: Spring Data Redis
Reference( https://spring.io/projects/spring-data-redis)
ⅳ- json处理:alibaba/fastjson
Reference(https://github.com/alibaba/fastjson)
实践内容
ⅰ- 分层: controller、service、mapper
ⅱ- 业务函数服务: 依赖注入
声明服务接口、实现服务方法、注册实现服务(添加注解**@Service**)、使用服务(构造器注入、自动注入、指定注入)
ⅲ- API实现: 添加控制类、编写API函数、公开路由
ⅳ- 代码自动生成
Reference(https://mp.baomidou.com/guide/generator.html)
注:实践视频播放到14:00时可跳至15:50(花了一分多钟找问题)
mapper:Mybatis概念,请参考官方说明
分层规范:参考服务规范
采用postgresql和mybatis-plus
<dependency>
<groupId>org.postgresqlgroupId>
<artifactId>postgresqlartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.2version>
dependency>
spring:
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://192.168.1.161:5432/bup_basedata_dev
username: postgres
password: *******
mybatis:
type-aliases-packag: com.biocome.basedata.entity
mapper-locations: classpath:mapper/*.xml
package com.biocome.basedata.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
*
* 用户
*
*
* @author yiquan
* @since 2021-03-05
*/
@Data //这个必须写 否则 自行实现每个字段 getter /setter
@EqualsAndHashCode(callSuper = false)
@TableName("bup_t_user") //这个必须写 数据库名称和类名称不一致
public class User implements Serializable { //Serializable 必须写 序列化用
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
@TableId
private String userId;
/**
* 用户名
*/
private String userName;
/**
* 密码
*/
private String password;
/**
* 单位GUID
*/
private String orgGuid;
// 省略
}
//0 添加mapper , 如果不是数据库服务 就不需要
public interface UserMapper extends BaseMapper<User> {
}
//1 声明服务接口,如果不是数据库仓库 就不需要 extends IService
public interface IUserService extends IService<User> {
String GenerateToken(User user);
}
//2 实现接口
@Service //3 注册服务 , 如果不是数据库服务 就不需要 extends ServiceImpl
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public String GenerateToken(User user) {
return "token";
}
}
//4 使用服务
public class UserController{
/* 自动注入 不推荐
* @Autowired
* private IUserService userService;
*/
private final IUserService userService;
//构造函数注入
public UserController(IUserService userService) {
this.userService = userService;
}
@GetMapping("/{uid}")
public ResponseEntity<User> Get(@PathVariable String uid) {
//使用mybatis-plus领域函数
User user = userService.getById(uid);
//使用自定义领域函数
String token = userService.GenerateToken(user);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(user);
}
}
//添加控制器
@RestController //添加Rest注解,注:RestController包含Controller、ResponseBody等注解
@RequestMapping("/api/v1/user") //声明类路由
public class UserController{
//Get
@GetMapping("/{uid}") //声明API路由,Get /api/v1/user/{uid} ,如果没有声明类路由 Get /{uid}
public ResponseEntity<User> Get(@PathVariable String uid) { //@PathVariable注解,获取路由数据
return ResponseEntity.ok(new User());
}
//Post
@PostMapping
public ResponseEntity<?> Create(@RequestBody CreateUser user){ //@RequestBody 获取Body数据
return ResponseEntity.ok(new User());
}
//Put
@PutMapping("/{uid}")
public ResponseEntity<?> Put(@PathVariable String uid, @RequestBody ChangeUser changeUser) {
return ResponseEntity.ok(new User());
}
//删除
@DeleteMapping("/{uid}")
public ResponseEntity<?> Delete(@PathVariable String uid) {
return ResponseEntity.ok(null);
}
//参数验证
@PostMapping("/login")
public ResponseEntity<?> Login(@Validated @RequestBody LoginUser loginUser){ //@Validated注解 进行参数验证
return new ResponseEntity("账号或则密码错误", HttpStatus.BAD_REQUEST);
}
/* 参数验证DTO
@Data
public class LoginUser {
@NotBlank(message = "登录名不能为空")
private String userName;
@NotBlank(message = "密码不能用空")
private String password ;
}
*/
}
拷贝示例工程中代码CodeGenerator,配置数据库后,运行后输入表名称即可。注:代码生成示例需要修改模块名称,Basedata中不需要。
参考说明:https://baomidou.com/guide/generator.html
示例代码
package com.biocome.generator;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
*
* 代码生成器
*
*
* @author yiquan
* @since 2021-02-22 10:41
**/
public class CodeGenerator {
/**
* 监听输入
*
* @param tip 监听输入项
* @return 输入内容
* @throws MybatisPlusException 输入异常
* @author yiquan
* @since 2021-02-22 10:58
**/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
/**
* 主函数
*
* @param args 启动参数
* @return 无
* @author yiquan
* @since 2021-02-22 10:58
* @update yiquan 2021-02-22 11:01 修改包生成格式
* @update yiquan 2021-02-22 11:00 更改从mysql改为postgre
**/
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("yiquan");
gc.setOpen(false);
//gc.setSwagger2(true); //实体属性 Swagger2 注解
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:postgresql://192.168.1.161:5432/bup_basedata_dev");
dsc.setDriverName("org.postgresql.Driver");
dsc.setUsername("postgres");
dsc.setPassword("******");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("basedata");//scanner("模块名"));
pc.setParent("com.biocome");
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
/*
cfg.setFileCreate(new IFileCreate() {
@Override
public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
// 判断自定义文件夹是否需要创建
checkDir("调用默认方法创建的目录,自定义目录用");
if (fileType == FileType.MAPPER) {
// 已经生成 mapper 文件判断存在,不想重新生成返回 false
return !new File(filePath).exists();
}
// 允许生成模板文件
return true;
}
});
*/
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
// 配置自定义输出模板
//指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
// templateConfig.setEntity("templates/entity2.java");
// templateConfig.setService();
// templateConfig.setController();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setTablePrefix("bup_t");
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
// 公共父类
strategy.setSuperControllerClass("BaseController");
// 写于父类中的公共字段
strategy.setSuperEntityColumns("id");
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
需要部署注册中心和网关(已经开发完毕,待整理)
实践内容一览
服务注册、服务发现、网关路由转发、服务通信(RPC)
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
<version>2.2.5.RELEASEversion>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
<version>2.2.5.RELEASEversion>
dependency>
spring:
application:
name: basedata # 应用名 - 开发时**需要修改**
cloud:
nacos:
discovery:
server-addr: 192.168.1.152:8848 # 注册中心地址 - 开发时**需要修改 **
metadata:
routes: "/api/v1/user/**,/api/v1/org/**" # 注册路由 - 开发时**需要修改**
router:
dataid: gateway_dynamic_route # 动态路由配置文件名 - 开发时无(按)需修改
group: DEFAULT_GROUP # 动态路由配置分组 - 开发时无(按)需修改
/服务名/对应服务的路由
。api/v1/user/xxxx
GET /api/v1/user/xxxx
GET /xxService/api/v1/user/xxxx
spring.cloud.gateway.enabled=false
.”补充资料1 引用gateway方式:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
补充资料2 代码示例:
查看代码-NameUtils
import java.util.regex.Pattern;
/**
* @author Spencer Gibb
*/
public final class NameUtils {
private NameUtils() {
throw new AssertionError("Must not instantiate utility class.");
}
/**
* Generated name prefix.
*/
public static final String GENERATED_NAME_PREFIX = "_genkey_";
private static final Pattern NAME_PATTERN = Pattern.compile("([A-Z][a-z0-9]+)");
public static String generateName(int i) {
return GENERATED_NAME_PREFIX + i;
}
}
查看代码-FilterDefinition
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import javax.validation.ValidationException;
import javax.validation.constraints.NotNull;
import org.springframework.validation.annotation.Validated;
import static org.springframework.util.StringUtils.tokenizeToStringArray;
/**
* @author Spencer Gibb
*/
@Validated
public class PredicateDefinition {
@NotNull
private String name;
private Map<String, String> args = new LinkedHashMap<>();
public PredicateDefinition() {
}
public PredicateDefinition(String text) {
int eqIdx = text.indexOf('=');
if (eqIdx <= 0) {
throw new ValidationException(
"Unable to parse PredicateDefinition text '" + text + "'" + ", must be of the form name=value");
}
setName(text.substring(0, eqIdx));
String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ",");
for (int i = 0; i < args.length; i++) {
this.args.put(NameUtils.generateName(i), args[i]);
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, String> getArgs() {
return args;
}
public void setArgs(Map<String, String> args) {
this.args = args;
}
public void addArg(String key, String value) {
this.args.put(key, value);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PredicateDefinition that = (PredicateDefinition) o;
return Objects.equals(name, that.name) && Objects.equals(args, that.args);
}
@Override
public int hashCode() {
return Objects.hash(name, args);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("PredicateDefinition{");
sb.append("name='").append(name).append('\'');
sb.append(", args=").append(args);
sb.append('}');
return sb.toString();
}
}
查看代码-PredicateDefinition
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import javax.validation.ValidationException;
import javax.validation.constraints.NotNull;
import org.springframework.validation.annotation.Validated;
import static org.springframework.util.StringUtils.tokenizeToStringArray;
/**
* @author Spencer Gibb
*/
@Validated
public class PredicateDefinition {
@NotNull
private String name;
private Map<String, String> args = new LinkedHashMap<>();
public PredicateDefinition() {
}
public PredicateDefinition(String text) {
int eqIdx = text.indexOf('=');
if (eqIdx <= 0) {
throw new ValidationException(
"Unable to parse PredicateDefinition text '" + text + "'" + ", must be of the form name=value");
}
setName(text.substring(0, eqIdx));
String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ",");
for (int i = 0; i < args.length; i++) {
this.args.put(NameUtils.generateName(i), args[i]);
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, String> getArgs() {
return args;
}
public void setArgs(Map<String, String> args) {
this.args = args;
}
public void addArg(String key, String value) {
this.args.put(key, value);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PredicateDefinition that = (PredicateDefinition) o;
return Objects.equals(name, that.name) && Objects.equals(args, that.args);
}
@Override
public int hashCode() {
return Objects.hash(name, args);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("PredicateDefinition{");
sb.append("name='").append(name).append('\'');
sb.append(", args=").append(args);
sb.append('}');
return sb.toString();
}
}
查看代码-RouteDefinition
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
import javax.validation.ValidationException;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.net.URI;
import java.util.*;
import static org.springframework.util.StringUtils.tokenizeToStringArray;
@Validated
public class RouteDefinition {
private String id;
@NotEmpty
@Valid
private List<PredicateDefinition> predicates = new ArrayList<>();
@Valid
private List<FilterDefinition> filters = new ArrayList<>();
@NotNull
private URI uri;
private Map<String, Object> metadata = new HashMap<>();
private int order = 0;
public RouteDefinition() {
}
public RouteDefinition(String text) {
int eqIdx = text.indexOf('=');
if (eqIdx <= 0) {
throw new ValidationException(
"Unable to parse RouteDefinition text '" + text + "'" + ", must be of the form name=value");
}
setId(text.substring(0, eqIdx));
String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ",");
setUri(URI.create(args[0]));
for (int i = 1; i < args.length; i++) {
this.predicates.add(new PredicateDefinition(args[i]));
}
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<PredicateDefinition> getPredicates() {
return predicates;
}
public void setPredicates(List<PredicateDefinition> predicates) {
this.predicates = predicates;
}
public List<FilterDefinition> getFilters() {
return filters;
}
public void setFilters(List<FilterDefinition> filters) {
this.filters = filters;
}
public URI getUri() {
return uri;
}
public void setUri(URI uri) {
this.uri = uri;
}
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
public Map<String, Object> getMetadata() {
return metadata;
}
public void setMetadata(Map<String, Object> metadata) {
this.metadata = metadata;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RouteDefinition that = (RouteDefinition) o;
return this.order == that.order && Objects.equals(this.id, that.id)
&& Objects.equals(this.predicates, that.predicates) && Objects.equals(this.filters, that.filters)
&& Objects.equals(this.uri, that.uri) && Objects.equals(this.metadata, that.metadata);
}
@Override
public int hashCode() {
return Objects.hash(this.id, this.predicates, this.filters, this.uri, this.metadata, this.order);
}
@Override
public String toString() {
return "RouteDefinition{" + "id='" + id + '\'' + ", predicates=" + predicates + ", filters=" + filters
+ ", uri=" + uri + ", order=" + order + ", metadata=" + metadata + '}';
}
}
查看代码-RegisterService
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@Slf4j
@Component
public class RegisterService implements ApplicationRunner {
@Value("${spring.application.name}")
private String applicationName;
@Value("${spring.cloud.nacos.discovery.metadata.routes}")
private String routes;
@Value("${spring.cloud.nacos.discovery.server-addr}")
private String nacosaddr;
@Value("${spring.cloud.nacos.router.dataid}")
private String dataid;
@Value("${spring.cloud.nacos.router.group}")
private String group;
private String SeriveId;
@Override
public void run(ApplicationArguments args) throws Exception {
SeriveId = "dynamic_" + applicationName;
ConfigService configService = getConfigService();
String content = configService.getConfig(dataid, group, 5000);
List<RouteDefinition> routeDefinitions = new ArrayList<>();
AtomicBoolean nacosHasValue = new AtomicBoolean(false);
AtomicReference<RouteDefinition> routeDefinitionRegister = new AtomicReference<>();
if (StringUtils.isNotBlank(content))
routeDefinitions = JSONObject.parseArray(content, RouteDefinition.class);
routeDefinitions.forEach(routeDefinitionItem -> {
if (routeDefinitionItem.getId().equals(SeriveId)) {
routeDefinitionRegister.set(routeDefinitionItem);
nacosHasValue.set(true);
}
});
if (!nacosHasValue.get()) {
RouteDefinition routeDefinition = getRouteDefinition();
routeDefinitionRegister.set(routeDefinition);
}
List<PredicateDefinition> predicateDefinitions = getPredicateDefinitions();
RouteDefinition routeDefinition = routeDefinitionRegister.get();
routeDefinition.setPredicates(predicateDefinitions);
routeDefinitions.removeIf(routeDefinitionItem -> routeDefinitionItem.getId().equals(SeriveId));
routeDefinitions.add(routeDefinition);
configService.publishConfig(dataid, group, JSONObject.toJSONString(routeDefinitions), "json");
}
private RouteDefinition getRouteDefinition() throws URISyntaxException {
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(SeriveId);
routeDefinition.setUri(new URI("lb://" + applicationName));
routeDefinition.setOrder(1);
return routeDefinition;
}
private List<PredicateDefinition> getPredicateDefinitions() {
List<PredicateDefinition> predicateDefinitions = new ArrayList<>();
PredicateDefinition predicateDefinition = new PredicateDefinition();
predicateDefinition.setName("Path");
Map<String, String> predicateArgs = new HashMap<>();
AtomicInteger i = new AtomicInteger();
Arrays.asList(routes.split(",")).forEach(route -> {
predicateArgs.put("pattern" + (i.getAndIncrement()), route);
});
predicateDefinition.setArgs(predicateArgs);
predicateDefinitions.add(predicateDefinition);
return predicateDefinitions;
}
private ConfigService getConfigService() throws NacosException {
Properties properties = new Properties();
properties.put("serverAddr", nacosaddr);
properties.setProperty("", "");
ConfigService configService = NacosFactory.createConfigService(properties);
return configService;
}
}
SpringCloud 构成
如果转发路由需要与服务路由保持一致,则需要使用动态路由,添加路由断言可以根据路由识别服务地址,详细参考2-实践i-服务注册、发现、网关使用方式、路由断言配置
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-dubboartifactId>
<version>2.2.5.RELEASEversion>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
<version>2.2.5.RELEASEversion>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
<version>2.2.5.RELEASEversion>
dependency>
dubbo:
application:
name: dubbo-spring-cloud-provider-basedata # 服务名 - 需更高
scan:
base-packages: com.biocome.basedata.service # 服务实现的路径 - 需更改
protocol:
name: dubbo # 协议 - 无需更改
port: -1
编写代码
package com.biocome.basedata.service.authorization;
public interface IAuthorizationServerProvider {
String GenerateToken(User user);
}
@DubboService(version = "1.0.0")
@Service
public class JWTAuthorizationService implements IAuthorizationServerProvider {
@Override
public String GenerateToken(User user) {
return "token";
}
}
dubbo:
protocol:
name: dubbo
port: -1
cloud:
subscribed-services: dubbo-spring-cloud-provider-basedata,dubbo-spring-cloud-provider-web # 声明依赖的服务
consumer:
timeout: 5000
check: false
编写代码
package com.biocome.basedata.service.authorization; // 命名空间不能更改
public interface IAuthorizationServerProvider {
boolean ValidToken(String token);
}
//使用
@RestController
public class UserBasedataController {
// 类似本地调用服务函数一样
@DubboReference(version = "1.0.0")
IAuthorizationServerProvider AuthorizationServerProvider;
@PostMapping("/token/check1")
public String IsValidToken1(@RequestBody CheckToken token) {
return AuthorizationServerProvider.ValidToken(token.getToken()) ? "有效" : "无效";
}
}
dubbo 官网地址:https://dubbo.apache.org/zh/
docker-compose -f standalone-derby.yaml up
standalone-derby.yaml
version: "2"
services:
nacos:
image: nacos/nacos-server:latest
container_name: nacos-standalone
environment:
- PREFER_HOST_MODE=hostname
- MODE=standalone
volumes:
- ./standalone-logs/:/home/nacos/logs
- ./init.d/custom.properties:/home/nacos/init.d/custom.properties
ports:
- "8848:8848"
/init.d/custom.properties/custom.properties
# metrics for prometheus
management.endpoints.web.exposure.include=*
默认账号密码:nacos/nacos
注意:METE-INF/MANIFEST.MF
目录必须放在\src\main\resources
下(原因:里面配置有入口类和依赖等)
打包为镜像:docker build -t bup/dubbo_basedata:0.0.1 .
运行:docker run --name java_basedata -p 30221:30221 bup/dubbo_basedata:0.0.1
# 依赖jdk8
FROM java:8
EXPOSE 30221
MAINTAINER James # 作者
WORKDIR /app # 运行路径
COPY /out/artifacts/dubbo_service_jar/ /app # 写对应的生成jar生成的目录
ENTRYPOINT ["java","-jar","dubbo_service.jar"]
bash bup_start.sh install
部署容器各文件内容
Dockerfile:
# 依赖jdk8
FROM java:8
EXPOSE 30221
MAINTAINER yiquan
WORKDIR /app
COPY . .
ENTRYPOINT ["java","-jar","dubbo_service.jar"]
docker-compose.yml
version: '3.4'
services:
services_basedata: # 服务名称
image: bup/dubbo_basedata:0.1 # 使用镜像
container_name: dubbo_basedata # 容器名称
restart: always # 保证开机自启
ports:
- "30221:30221" # http映射端口
env_file:
- dubbo_basedata_env.env # 环境变量文件,参考 配置 管理说明
volumes:
- /etc/localtime:/etc/localtime # 容器内部时间与宿主机一致
logging:
driver: "json-file" # 日志格式
options:
max-size: "200M" # 限制日志文件大小
max-file: "10" # 限制日志文件个数
networks:
- bup-networks-v1 # 网络
networks:
bup-networks-v1:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.30.0.0/16
环境变量文件,配置读取文件配置的环境变量,而不是系统配置的环境。(目的:数据库等配置正式环境和开发环境不一样,需要修改环境变量文件而不是重新制作jar包)
例如application.yml中
spring:
cloud:
nacos:
discovery:
server-addr: ${RegisterServerAddr:127.0.0.1:8848} # RegisterServerAddr需要配置正式环境中的地址
那么环境变量文件(dubbo_basedata_env.env)中内容为:
RegisterServerAddr=192.168.1.152:8848
启动文件bup_start.sh
#! /bin/bash
command=$1 #命令
container_name="dubbo_basedata"
image_name="bup/dubbo_basedata:0.1"
if [ "$command" = "" ];then
docker-compose -p bup-compose -f docker-compose.yml down
docker-compose -p bup-compose -f docker-compose.yml up -d
else
docker-compose -p bup-compose -f docker-compose.yml down
docker rm -f $container_name
docker rmi $image_name
docker build -t $image_name .
docker-compose -p bup-compose -f docker-compose.yml up -d
fi
添加项目
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
使用
@Slf4j // 添加注解
@RestController
public class UserBasedataController {
@PostMapping("/token/check1")
public String IsValidToken1(@RequestBody CheckToken token) {
log.debug("验证登录"); //编写日志
return "";
}
}
添加全局异常拦截
package com.biocome.basedata.config;
import com.biocome.basedata.dto.*;
import com.sun.org.apache.bcel.internal.generic.ATHROW;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* 统一异常处理
*
* @author yiquan
* @since 2021-3-2
*/
@Slf4j
@RestControllerAdvice
//public class GlobalExceptionHandlerConfig implements ResponseBodyAdvice {
public class GlobalExceptionHandlerConfig {
/**
* 处理全局异常
*
* @param request
* @param exception
* @return
*/
@ExceptionHandler(value = Exception.class)
public ResponseEntity handlerGlobeException(HttpServletRequest request, Exception exception) {
log.error(exception.getMessage(), exception);
return new ResponseEntity(new ResponseResult(request, HttpStatus.INTERNAL_SERVER_ERROR, "服务器异常,请稍后重试"), HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
* HTTP方法无法使用
*
* @param request
* @param exception
* @return
*/
@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
public String handlerHTTPGlobeException(HttpServletRequest request, HttpRequestMethodNotSupportedException exception) throws HttpRequestMethodNotSupportedException {
throw exception;
}
/**
* 参数合法性校验异常
*
* @param exception
* @return
*/
@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity validationBodyException(HttpServletRequest request, MethodArgumentNotValidException exception) {
StringBuffer buffer = new StringBuffer();
BindingResult result = exception.getBindingResult();
if (result.hasErrors()) {
List<ObjectError> errors = result.getAllErrors();
errors.forEach(p -> {
FieldError fieldError = (FieldError) p;
log.debug("Data check failure : object{ " + fieldError.getObjectName() + " },field{ " + fieldError.getField() +
" },errorMessage{ " + fieldError.getDefaultMessage() + " }");
buffer.append(fieldError.getDefaultMessage()).append(",");
});
}
String message = buffer.toString().substring(0, buffer.toString().length() - 1);
ResponseResult responseResult = new ResponseResult(request, HttpStatus.BAD_REQUEST, message);
return new ResponseEntity(responseResult, HttpStatus.BAD_REQUEST);
}
}
添加Maven引用
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.75version>
dependency>
String userJson = JSON.toJSONString(new User());
User user = JSON.parseObject("{'userName':'xxxx'}", User.class);
List<User> users = JSONObject.parseArray("[{'userName':'xxxx'},{'userName':'yyyy'}]", User.class);
User user = userService.Get("xxxxxxxx");
UserInfoDto userInfoDto = new UserInfoDto();
BeanUtils.copyProperties(user, userInfoDto);
//可以扩展
/**
* bean 映射扩展
*
* @param clazz 类型
* @param source 转换源
* @param 类型
* @return 生成实体 T
*/
public static <T> T copyProperties(Class<T> clazz, Object source) {
T target = ClassUtils.newInstance(clazz);
BeanUtils.copyProperties(source, target);
return target;
}
/**
* 转换集合
*
* @param clazz 需要转换目标对象
* @param sources 转换数据
* @param target
* @param source
* @return
*/
public static <T, S> Collection<T> copyListProperties(Class<T> clazz, Collection<S> sources) {
Collection<T> targets = new ArrayList<>();
sources.forEach((source) -> {
T target = ClassUtils.newInstance(clazz);
BeanUtils.copyProperties(source, target);
targets.add(target);
});
return targets;
}
/**
* 用户API
*
* @author yiquan
* @since 2021-02-22
*/
@RestController
@RequestMapping("/api/v1/user")
public class UserController {
@Autowired
private HttpServletRequest request;
private final IUserService userService;
public UserController(IUserService userService) {
this.userService = userService;
}
private User getCurrentUser() {
String userInfoJson = request.getHeader("userinfo");
if(StringUtils.isNotBlank(userInfoJson))
return JSON.parseObject(userInfoJson,User.class);
return null;
}
/**
* 创建用户
*
* @param user 用户Dto
* @return 返回添加的用户
*/
@PostMapping
public ResponseEntity<?> Create(@RequestBody CreateUser user) {
String createUserUid = Optional.ofNullable(getCurrentUser()).get().getUserId();
boolean isSuccess = true;
return isSuccess ? ResponseEntity.ok(checkResult.Entity) :
new ResponseEntity("处理完成,没有添加成功,请稍后重试", HttpStatus.NOT_ACCEPTABLE);
}
原理:通过网关登录,用户服务产生token,网关会保存该值。后续其他请求网关会根据token解析出用户信息userinfo(Json格式,该对象为用户服务
生成token所用的用户对象实体),转发请求http header会带上该json值,各服务按需解析,过程如下:
详细说明参考官网说明文档
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
package com.biocome.nacosgateway.unitity;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* RedisConfig 配置保存对象为二进制
*
* @author yiquan
* @since 2021-3-8
*/
@Configuration
public class RedisConfig {
/**
* redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类
*
* @param redisConnectionFactory redis连接工厂类
* @return RedisTemplate
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 使用Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 设置value的序列化规则和 key的序列化规则
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
public class UserController extends BaseController {
private final IUserService userService;
private final IOrgService orgService;
private final IAuthorizationServerProvider authorizationServerProvider;
@Resource
private RedisTemplate<String, User> redisTemplate;
public UserController(IUserService userService,
IOrgService orgService,
IAuthorizationServerProvider authorizationServerProvider) {
this.userService = userService;
this.orgService = orgService;
this.authorizationServerProvider = authorizationServerProvider;
}
@GetMapping("/{uid}")
public ResponseEntity<User> get(@PathVariable String uid) {
String key = "user_" + uid;
ValueOperations<String, User> operations = redisTemplate.opsForValue();
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
//获取缓存
User user = operations.get(key);
return ResponseEntity.ok(user);
}
User user = userService.getById(uid);
if (user == null) {
return ResponseEntity.notFound().build();
}
//保存缓存
operations.set(key, user, 10, TimeUnit.SECONDS);
return ResponseEntity.ok(user);
}
@DeleteMapping("/{uid}")
public ResponseEntity<?> delete(@PathVariable String uid) {
User user = userService.getById(uid);
if (user == null) {
return new ResponseEntity(HttpStatus.NOT_FOUND);
}
if (user.getUserName() == "admin") {
return createResponseResult("不能删除管理员", HttpStatus.BAD_REQUEST);
}
boolean isSuccess = userService.removeById(uid);
if (isSuccess) {
String key = "user_" + uid;
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
//删除缓存
redisTemplate.delete(key);
}
}
return isSuccess ? ResponseEntity.ok(null) :
createResponseResult("处理完成,没有删除成功,请稍后重试", HttpStatus.NOT_ACCEPTABLE);
}
参考Spring Framework
package com.biocome.basedata.scheduling;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.biocome.basedata.entity.User;
import com.biocome.basedata.service.IUserService;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Optional;
/**
* Task Scheduling 任务调度示例
*
* @author yiquan
* @since 2021-03-24
*/
@Component
@EnableScheduling
@EnableAsync
public class PrintScheduling {
private final IUserService userService;
public PrintScheduling(IUserService userService) {
this.userService = userService;
}
/**
* 每隔两秒打印一个时间字符串
*/
@Async
@Scheduled(fixedDelay = 2000)
public void printStringDelayTwoSecond() {
System.out.println("Scheduling示例,每隔两秒打印一个时间字符串:" + new Date());
}
/**
* 每小时0分0秒 打印admin用户的Id
*/
@Async
@Scheduled(cron = "0 0 * * * *")
public void printAdminUserIdByCron() {
User user = userService.getOne(new QueryWrapper<User>().lambda().eq(User::getUserName, "admin"));
System.out.println("Scheduling示例, 每小时0分0秒 打印admin用户的Id:" + Optional.ofNullable(user).map(u -> u.getUserId()).orElse("空"));
}
/**
* 使用application配置, 每天零点 打印admin1用户的Id
* basedata.scheduling.print-userid-cron : 0 0 0 * * *
*/
@Async
@Scheduled(cron = "${basedata.scheduling.print-userid-cron}")
public void printAdminUserUidByConfigCron() {
User user = userService.getOne(new QueryWrapper<User>().lambda().eq(User::getUserName, "admin1"));
System.out.println("Scheduling示例, 每天零点 打印admin1用户的Id:" + Optional.ofNullable(user).map(u -> u.getUserId()).orElse("空"));
}
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
package com.biocome.basedata.config;
import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
/**
* mq配置
*
* @author yiquan
* @since 2021-03-25
* @version 1.0
*/
@Configuration
public class RabbitmqConfig {
public static final String EXCHANGE_TOPICS_INFORM="exchange_topics_inform";
public static final String QUEUE_INFORM_USER = "queue_inform_user";
public static final String QUEUE_INFORM_ORG = "queue_inform_org";
public static final String INFORM_USER_CREATE ="inform.user.create";
public static final String INFORM_USER_GET ="inform.user.get";
public static final String INFORM_ORG_CREATE ="inform.org.create";
/**
* 声明交换机
* @return
*/
@Bean(EXCHANGE_TOPICS_INFORM)
public Exchange EXCHANGE_TOPICS_INFORM(){
return ExchangeBuilder.topicExchange(EXCHANGE_TOPICS_INFORM).durable(true).build();
}
/**
* 生产者 确保消息实体用json格式
* @return
*/
@Bean
public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
return new Jackson2JsonMessageConverter();
}
}
public class UserController {
private final IUserService userService;
private final RabbitTemplate rabbitTemplate;
public UserController(IUserService userService,
RabbitTemplate rabbitTemplate) {
this.userService = userService;
this.rabbitTemplate = rabbitTemplate;
}
public void get(String uid) {
User user = userService.getById(uid);
rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPICS_INFORM, RabbitmqConfig.INFORM_USER_GET, user);
}
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
package com.biocome.basedata.bus;
import com.biocome.basedata.entity.Org;
import com.biocome.basedata.entity.User;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
public class UserReceiveHandler {
public static final String EXCHANGE_TOPICS_INFORM="exchange_topics_inform";
public static final String QUEUE_INFORM_USER = "queue_inform_user";
public static final String QUEUE_INFORM_ORG = "queue_inform_org";
public static final String INFORM_USER_CREATE ="inform.user.create";
public static final String INFORM_USER_GET ="inform.user.get";
public static final String INFORM_ORG_CREATE ="inform.org.create";
/**
* 交换机EXCHANGE_TOPICS_INFORM上,申明队列 QUEUE_INFORM_USER
* 订阅INFORM_USER_CREATE, INFORM_USER_GET, "inform.user.change.*" 消息
*
* @param user
* @param message
*/
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange(name = EXCHANGE_TOPICS_INFORM, type = ExchangeTypes.TOPIC),
value = @Queue(name = QUEUE_INFORM_USER, durable = "true"),
key = {INFORM_USER_CREATE, INFORM_USER_GET, "inform.user.change.*"}))
public void receiveUser(User user, Message message) {
System.out.println("收到User消息,key = " + message.getMessageProperties().getReceivedRoutingKey() + " msg = " + user);
}
/**
* 交换机EXCHANGE_TOPICS_INFORM上,申明队列 QUEUE_INFORM_ORG
* 订阅INFORM_ORG_CREATE 消息
*
* @param org
* @param message
*/
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange(name = EXCHANGE_TOPICS_INFORM, type = ExchangeTypes.TOPIC),
value = @Queue(name = QUEUE_INFORM_ORG, durable = "true"),
key = INFORM_ORG_CREATE))
public void receiveOrgGet(Org org, Message message) {
System.out.println("收到Org消息 msg = " + org);
}
}
备注:简单的代码做简单注释,注释内容不大于10个字即可,另外,持久化对象或VO对象的getter、setter方法不需加注释。
/**
* 验证路径接口
*
* @author yiquan
* @since 2021-3-9
*/
public class PathVerifyService {
/**
* 用户ID
*/
private String userId;
/**
* 用户名
*/
private String userName;
/**
* 判断路径是否需要鉴权
*
* @param path
* @return
*/
Boolean shouldFilter(String path) { return true; }
}
备注:注释格式自行定义,注释内容准确简洁
@author 对类的说明 标明开发该类模块的作者
@version 对类的说明 标明该类模块的版本
@see 对类、属性、方法的说明 参考转向,也就是相关主题
@param 对方法的说明 对方法中某参数的说明
@return 对方法的说明 对方法返回值的说明
@exception 对方法的说明 对方法可能抛出的异常进行说明
使用layered architecture(分层架构,参考文档 Buschmann, F., et al., 1996. Pattern-Oriented Software Architecture: A System ofPatterns. Wiley.C,第31-55页)。
示例,银行转账:
防止业务代码分散,造成查看、分析困难等详细介绍请参考《领域驱动设计-软件核心复杂性应对之道》
,这里仅列举部分说明:
没有唯一、可解决所有服务划分的方法
,先看看大佬们的方法。
业务维度
进行拆分。业务关联密切
或功能较独立
的适合拆分为一个单独的微服务。简单粗暴,有底气那就拆
组织结构
要适应性优化可扩展性
发布频率
从上面不同的拆分方式可以看出,拆分服务没有绝对的标准
,只有合理
才是标准,业务、人员组织、公司环境、不同的时间段等都可能影响服务的划分。如果硬套可能会遇到矛盾的地方,假使不到10个人的团队,偏偏拆分数十个或则更多,而人员配置一般为3个最优(参考:三个火枪手的观点)。以下提供一种划分思路。
聚合模式
将领域模型组织为聚合的集合,即一个边界内领域对象的集群。
识别聚合边界的原则
Restful指南:
Architectural Styles and
the Design of Network-based Software Architectures
Representational State Transfer (REST)
Post
/api/v1/user
{
"userName": "biocome",
"password": "biocomeOtherWords",
"orgGuid": "7e7f4ddd-3a51-4af4-8555-ec4d548f",
"roleId": "2342342234234234"
}
Status: 200 OK
Body
{
"userId": "aaf519e7-7ad7-4eeb-8684-c653d5c57ea0",
"userName": "6369",
"password": "7e2fad22e9bde6eb3c1241e59d5a6475410d1c866881850314b72e96ea836788",
"orgGuid": "7e7f4ddd-3a51-4af4-8555-ec4d548f",
"sex": null,
"mobile": null,
"email": null,
"lastLoginTime": null,
"createTime": 1615537423609,
"updateTime": 1615537423609,
"enableFlag": 1,
"lastLoginIp": null,
"nameCn": null,
"lastFailTime": null,
"failCount": null
}
DELETE
/api/v1/user/{uid}
Status: 200 OK
HTTP Status:404 Not Found
PUT
/api/v1/user/{uid}
GET
/api/v1/user/{uid}
POST
/api/v1/user/page
{
"pageIndex": 1,
"pageSize": 10,
"userName": "admin"
}
{
"current": 1,
"pageSize": 10,
"total": 1,
"records": [
{
"userId": "b4600fb2-61c3-4786-8ba1-9fc5387a8d17",
"orgGuid": "",
"orgName": null,
"sex": "SEX0001",
"mobile": null,
"email": null,
"lastLoginTime": 1615532617422,
"createTime": null,
"updateTime": null,
"lastLoginIp": "192.168.4.239",
"nameCn": "超级管理员"
}
],
"totalPage": 1
}
Status: 401 UNAUTHORIZED
{
"error": "UNAUTHORIZED",
"message": "Invalid authentication credentials",
"path": "/api/v1/user",
"status": 401,
"timestamp": 1615537147633
}
Status: 400 Bad Request
{
"status": 400,
"error": "BAD_REQUEST",
"message": "密码不能用空",
"timestamp": "2021-03-12T08:21:36.327+00:00",
"path": "/api/v1/user/login"
}
HTTP Status:404 Not Found
{
"timestamp": "2021-03-12T08:20:46.930+0000",
"path": "/api/v1/user/login",
"status": 504,
"error": "Gateway Timeout",
"message": "Response took longer than configured timeout",
"requestId": "9287d396-10"
}
Status: 500 Internal Server Error
{
"timestamp": "2021-02-24T01:38:24.706+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "",
"path": "/api/v1/user/search"
}
https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html
文件 -> 设置 -> 快捷键 -> 切换为 Visual Studio
除了更换其他IDE外,自带Window快捷键有
填入阿里云的镜像
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<mirrors>
<mirror>
<id>alimavenid>
<mirrorOf>centralmirrorOf>
<name>aliyun mavenname>
<url>http://maven.aliyun.com/nexus/content/repositories/central/url>
mirror>
<mirror>
<id>repo1id>
<mirrorOf>centralmirrorOf>
<name>Human Readable Name for this Mirror.name>
<url>http://repo1.maven.org/maven2/url>
mirror>
<mirror>
<id>repo2id>
<mirrorOf>centralmirrorOf>
<name>Human Readable Name for this Mirror.name>
<url>http://repo2.maven.org/maven2/url>
mirror>
<mirror>
<id>repoid>
<mirrorOf>centralmirrorOf>
<name>Human Readable Name for this Mirror.name>
<url>http://repo.maven.org/maven2/url>
mirror>
mirrors>
settings>
右键工程 ——> Maven -> 重新加载项目
一定是配置和Maven引用错误
请拷贝示例项目,在此基础上改;
请拷贝示例项目,在此基础上改;
请拷贝示例项目,在此基础上改;
C# #region和#endregion
Java //region和//endregion
参考Java Package 教程
Package names are written in all lower case to avoid conflict with the names of classes or interfaces.
参考Java method 教程
Naming a Method
Although a method name can be any legal identifier, code conventions restrict method names. By convention, method names should be a verb in lowercase or a multi-word name that begins with a verb in lowercase, followed by adjectives, nouns, etc. In multi-word names, the first letter of each of the second and following words should be capitalized. Here are some examples:
run
runFast
getBackground
getFinalData
compareTo
setX
isEmpty
Typically, a method has a unique name within its class. However, a method might have the same name as other methods due to method overloading.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.4.3version>
<relativePath/>
parent>
<groupId>com.biocomegroupId>
<artifactId>dubbo_serviceartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>dubbo_servicename>
<description>基础数据管理平台description>
<packaging>jarpackaging>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
<spring-cloud.version>2020.0.1spring-cloud.version>
<spring-cloud-alibaba.version>2.2.5.RELEASEspring-cloud-alibaba.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${spring-cloud-alibaba.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-dubboartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-validationartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.2version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>org.freemarkergroupId>
<artifactId>freemarkerartifactId>
<version>2.3.31version>
dependency>
<dependency>
<groupId>cn.licoygroupId>
<artifactId>encrypt-body-spring-boot-starterartifactId>
<version>1.0.4.RELEASEversion>
dependency>
<dependency>
<groupId>org.postgresqlgroupId>
<artifactId>postgresqlartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.75version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>5.3.4version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
server:
port: 30221
spring:
application:
name: basedata
cloud:
nacos:
discovery:
server-addr: 192.168.1.152:8848
metadata:
routes: "/api/v1/user/**,/api/v1/org/**"
router:
dataid: gateway_dynamic_route
group: DEFAULT_GROUP
main:
allow-bean-definition-overriding: true
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://192.168.1.161:5432/bup_basedata_dev
username: postgres
password: *******
redis:
host: 192.168.1.152
port: 6379
database: 0
lettuce:
shutdown-timeout: 200ms
dubbo:
application:
name: dubbo-spring-cloud-provider-basedata
scan:
base-packages: com.biocome.basedata.service
protocol:
name: dubbo
port: -1
mybatis:
type-aliases-packag: com.biocome.basedata.entity
mapper-locations: classpath:mapper/*.xml
user:
login:
access-token-expire-timespan: 3600