精华内容
下载资源
问答
  • 车辆网项目架构设计

    千次阅读 2020-12-18 09:42:54
    车辆网项目架构设计系统概述系统架构图组件功能简介项目工程结构Authren认证服务器后台服务器配置OpenFeign工程配置网关项目接收终端数据项目轨迹解析服务 系统概述 本系统主要使用Spring Cloud技术,包含的模块 ...

    系统概述

    本系统主要使用Spring Cloud技术,包含的模块 Gateway(网关),Nacos(微服务注册与发现),OpenFeign(HTTP+Restful客户端),Hystrix(断路器),ribbon(负载均衡),Security+OAuth2(安全认证),Kafka(消息队列),MybatisPlus(对象关系映射),Redis(缓存数据库),Netty等组件。

    系统架构图

    车联网系统架构图

    组件功能简介

    Gateway:Web服务的统一入口,进行消息的转发和限流操作
    Nacos:微服务的注册中心和数据中心,提供服务的发现和注册,数据的统一配置
    OpenFeign:HTTP+Restfull客户端,实现服务之间的调用
    Hystrix:断路器,实现服务的熔断和降级
    Ribbon:微服务调用的负载均衡
    Security+OAuth2:提供微服务调用的认证和授权
    Kafka:消息中间件,缓存终端数据,支持大并发
    MybatisPlus:对象关系映射,用于访问数据库
    Redis:数据缓存服务器,内存数据库,并发量大
    Netty:JavaNio架构,实现终端的Socket连接,支持更大的连接数

    项目工程结构

    在这里插入图片描述

    • obd车联网项目
      • auth 认证服务器
      • cloudcore Spring cloud 核心项目
      • common 工具类项目
      • nettysocket 终端数据接收项目
      • obd-feign-api OpenFeign接口项目
      • obd-feign-client OpenFeign客户端项目
      • obd-gateway 网关
      • obd-member-auth app账户认证中心
      • obd-task 定时任务
      • obd-terminal-simulator 终端模拟器
      • obd-third-park 三方服务项目
      • obd-zhb 真惠保项目
        • 真惠保APP服务项目
        • 真惠保后台管理项目
      • portal 车辆网后台项目
      • protoolanalysis 协议分析器

    Authren认证服务器

    • 客户端认证配置
    @EnableAuthorizationServer
    @Configuration
    public class AuthServerConfig extends AuthorizationServerConfigurerAdapter{
    	@Autowired
    	AuthenticationManager authenticationManager;
    	@Autowired
    	RedisConnectionFactory connectionFactory;
    	@Autowired
    	private DataSource dataSource;
    	@Override
    	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    		clients.jdbc(dataSource);
    		
    	}
    	
    	//配置AuthorizationServer tokenServices
    	@Override
    	public void configure(AuthorizationServerEndpointsConfigurer endpoints)throws Exception {
    		endpoints.tokenStore(redisTokenStore())
    		.accessTokenConverter(accessTokenConverter())
    		.authenticationManager(authenticationManager)
    		//禁用刷新令牌
    		.reuseRefreshTokens(false);
    	}
    	//定义TokenStore  使用redis存储token
    	@Bean
    	public TokenStore redisTokenStore() {
    		RedisTokenStore redisTokenStore = new RedisTokenStore(connectionFactory);
    		//token key生成规则   
    		redisTokenStore.setAuthenticationKeyGenerator(oAuth2Authentication -> UUID.randomUUID().toString());
    		return redisTokenStore;
    	}
    	//token 封装
    	@Bean
    	public JwtAccessTokenConverter accessTokenConverter() {
    		JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    		converter.setSigningKey("******");
    		return converter;
    	}
    	
    	//认证请求设置
    	@Override
    	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    		//允许所有人请求令牌
    		//以验证的客户端才能请求check_token
    		security.tokenKeyAccess("permitAll()")
    		.checkTokenAccess("isAuthenticated()")
    		.allowFormAuthenticationForClients();
    	}
    }
    
    • 登录账户认证设置
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    	@Autowired
    	private UserDetailsService userDetailsService;
    	@Override
    	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    		//设置服务认证提供者
    		auth.authenticationProvider(authenticationProvider());
    	}
    	 /**
         * @return 封装身份认证提供者
         */
        @Bean
        public DaoAuthenticationProvider authenticationProvider() {
            DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
            //设置用户加载服务类
            authenticationProvider.setUserDetailsService(userDetailsService); 
            //设置加密类 
            authenticationProvider.setPasswordEncoder(passwordEncoder()); 
            return authenticationProvider;
        }
    
    	@Bean
    	@Override
    	public AuthenticationManager authenticationManagerBean() throws Exception {
    		//使用父级认证管理器
    		AuthenticationManager manager = super.authenticationManagerBean();
    		return manager;
    	}
    	
    	@Override
    	protected void configure(HttpSecurity http) throws Exception {
    		//允许访问/oauth授权接口
    		http.csrf().disable()
    		//设置会话管理器,不是用HttpSession
    		.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    		.and()
    		.requestMatchers().anyRequest()
    		.and()
    		.formLogin().permitAll()
    		.and()
    		.authorizeRequests()
    		//调用认证接口 不需要认证
    		.antMatchers("/oauth/*").permitAll()
    		.and();
    	}
    	
    	//配置密码解码器
    	@Bean
    	public BCryptPasswordEncoder passwordEncoder() {
    		return new MyPasswordEncoder();
    	}
    
    }
    
    • 用户加载服务类设置
    @Service
    public class MyUserDetailsService implements UserDetailsService {
    	@Autowired 
    	private SUserInfoMapper userInfoMapper;
    	
    	@Override
    	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    		QueryWrapper<SUserInfo> qw = new QueryWrapper<SUserInfo>();
    		qw.eq("userName", username);
    		qw.ne("status", 9);
    		SUserInfo sysUser = userInfoMapper.selectOne(qw);
    		if(sysUser == null) {
    			throw new UsernameNotFoundException("用户不存在");
    		}
    		User user = new User();
    		user.setSysUser(sysUser);
    		//不提供授权
    		user.setAuthorities(new ArrayList<>());
    		
    		return user;
    	}
    }
    

    后台服务器配置

    • 资源服务器设置
    @Configuration
    @EnableResourceServer//开启资源服务器
    @EnableGlobalMethodSecurity(prePostEnabled = true)//开启方法级别的校验  https://www.cnblogs.com/felordcn/p/12142497.html
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter{
    	@Autowired
    	RestTemplate resourceRestTemplate;
    	//本地授权服务
    	@Autowired
    	MyLocalUserAuthoritiesService  userAuthoritiesService;
    	@Override
    	public void configure(ResourceServerSecurityConfigurer resources) {
    		resources
    			.tokenStore(new JwtTokenStore(accessTokenConverter()))
    			.stateless(true);
    		//配置RemoteTokenServices, 用于向AuthorizationServer验证令牌
    		MyRemoteTokenServices tokenServices = new MyRemoteTokenServices(userAuthoritiesService);
    		tokenServices.setAccessTokenConverter(accessTokenConverter());
    		//为restTemplate配置异常处理器,忽略400错误
    		resourceRestTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
    			@Override
    			//忽略 400
    			public void handleError(ClientHttpResponse response) throws IOException {
    				if(response.getRawStatusCode() != 400) {
    					super.handleError(response);
    				}
    			}
    		});
    		tokenServices.setRestTemplate(resourceRestTemplate);
    		//设置认证服务器地址
    		tokenServices.setCheckTokenEndpointUrl("http://"+NacosServerHostConstant.AUTH_SERVER_NAME+NacosServerHostConstant.AUTH_SERVER_ADDRESS+"/oauth/check_token");
    		//客户端id
    		tokenServices.setClientId(OAuth2ClientEnum.OBD_PORTAL.getClientId());
    		//客户端密码
    		tokenServices.setClientSecret(OAuth2ClientEnum.OBD_PORTAL.getPassword());
    		//无状态
    		resources.tokenServices(tokenServices).stateless(true);
    		//设置资源服务id
    		resources.resourceId(OAuth2ClientEnum.OBD_PORTAL.getClientId());
    	}
    	//token封装
    	@Bean
    	public JwtAccessTokenConverter accessTokenConverter() {
    		JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    		converter.setSigningKey("*****");
    		return converter;
    	}
    	@LoadBalanced
        @Bean
        public RestTemplate resourceRestTemplate() {
            return new RestTemplate();
        }
        //资源请求路径设置
    	@Override
    	public void configure(HttpSecurity http) throws Exception {
    		//允许跨域
    		 http.cors();
    		//配置资源服务器拦截规则
    		http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    		.and()
    		.requestMatchers().anyRequest()
    		.and()
    		.anonymous()
    		.and()
    		.authorizeRequests()
    		//设置不需要认证的请求路径
    		.antMatchers("/login/loginByPassword.vue","/**/*.vueNologin").permitAll()
    		.anyRequest().authenticated()
    		.and()
    		.exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
    		
    	}
    }
    
    • 本地授权服务
    @Component
    public class MyLocalUserAuthoritiesServiceImpl implements MyLocalUserAuthoritiesService {
    	@Autowired
    	private SUserInfoMapper sysUserMapper;
    	@Autowired
    	private SMenuMapper sysMenuMapper;
    	/**
    	 * redis客户端
    	 */
    	@Autowired
    	private RedisClient redisClient;
    	
    	@SuppressWarnings("unchecked")
    	@Override
    	public List<String> loadUserAuthoritiesByUserName(String accessToken ,String userName) {
    		//缓存中获取权限列表
    		List<String>  authorityList = (List<String>) redisClient.getValue(RedisKeyPreConstant.USER_LOGIN_OAUTH, accessToken);
    		//缓存中没有 从数据库中获取
    		if(authorityList == null) {
    			QueryWrapper<SUserInfo> qw = new QueryWrapper<SUserInfo>();
    			qw.eq("userName", userName);
    			SUserInfo sysUser = sysUserMapper.selectOne(qw);
    			if(sysUser == null) {
    				throw new UsernameNotFoundException("用户不存在");
    			}
    			authorityList = sysMenuMapper.getPermsListByUserId(sysUser.getUserId());
    			//获取token对象
    			AuthAccessToken saveToken = (AuthAccessToken) redisClient.getValue(RedisKeyPreConstant.USER_LOGIN_TOKEN, accessToken);
    			if(saveToken != null) {
    				//有效时间和token的有效时间一直
    				redisClient.setValue(RedisKeyPreConstant.USER_LOGIN_OAUTH, accessToken,authorityList,saveToken.getExpires_in()-(System.currentTimeMillis()-saveToken.getCreate_time()));
    			}else {
    				//设置默认的有效时间
    				redisClient.setValue(RedisKeyPreConstant.USER_LOGIN_OAUTH, accessToken,authorityList,Constant.AUNTH_MESSAGE_IN_REDIS_TIME);
    			}
    		}
    		return authorityList;
    	}
    }
    
    • 控制类设置
    @RestController
    @RequestMapping("sys/user")
    public class UserController extends BaseController{
    	@Autowired
    	private UserService userService;
    	/**
    	 * 用户分页查询
    	 * @param loginUser 登录用户
    	 * @param userName 用户名
    	 * @param pageModel 分页参数
    	 * @return
    	 */
    	@RequestMapping("getSysUserPageList.vue")
    	//设置求情权限
    	@PreAuthorize("hasAuthority('user:view')")
    	//设置事物级别为只读
    	@Transactional(readOnly = true)
    	public ReturnModel getUserPageList(@ModelAttribute("loginUser") LoginUser loginUser,String userName,String useMan,String linkPhone,Date beginTime,Date endTime,PageModel pageModel) {
    		ReturnModel returnModel = new ReturnModel();
    		returnModel.setData(userService.getPageList(getLoginUserOrganizationFullId(loginUser),userName, useMan, linkPhone, beginTime, endTime, pageModel));
    		return returnModel;
    	}
    	/**
    	 * 新增用户
    	 * @param loginUser 登录用户
    	 * @param user 添加用户对象
    	 * @return
    	 */
    	@PostMapping("addSysUser.vue")
    	//开启事物
    	@Transactional
    	//设置请求权限
    	@PreAuthorize("hasAuthority('user:add')")
    	public ReturnModel addRole(HttpServletRequest request,SUserInfo user,/*自动注入登录用户对象*/@ModelAttribute("loginUser") LoginUser loginUser) {
    		ReturnModel returnModel = new ReturnModel();
    		SUserInfo oldUser = userService.getUserByUserName(user.getUserName());
    		if(oldUser != null) {
    			returnModel.setResultCode(ResponseCodeConstant.EXISTED);
    			returnModel.setResultMessage("用户名名称不能重复");
    			return returnModel;
    		}
    		oldUser = userService.getUser(user.getUseMan(),user.getOrganizeId());
    		if(oldUser != null) {
    			returnModel.setResultCode(ResponseCodeConstant.EXISTED);
    			returnModel.setResultMessage("使用人不能重复");
    			return returnModel;
    		}
    		//正常状态
    		user.setStatus(1);
    		user.setCreaterId(loginUser.getUser().getUserId());
    		user.setCreaterName(loginUser.getUser().getUseMan());
    		user.setOperatorId(loginUser.getUser().getUserId());
    		user.setOperatorName(loginUser.getUser().getUseMan());
    		userService.addUser(user);
    		return returnModel;
    	}
    }
    
    • 服务类设置
    @Service
    public class UserService {
    	@Autowired
    	private SUserInfoMapper userInfoMapper;
    	/**
    	 * 添加用户信息
    	 * @param user 用户对象
    	 * @return
    	 */
    	 //添加日志注解   查询功能可以不添加
    	@Log("新增用户")
    	public int addUser(SUserInfo user) {
    		Date now = new Date();
    		user.setCreateTime(now);
    		user.setModifiedTime(now);
    		return userInfoMapper.insert(user);
    	}
    }
    

    OpenFeign工程配置

    • 接口定义
    @RequestMapping("feign/location")
    public interface IFeignLocationService {
        /**
         * 根据矩阵获及车辆id列表取gps信息
         *
         * @param organizeId   机构Id
         * @param bounds  矩阵   右上角精度,右上角维度|左下角精度,左下角维度  
         * @return
         */
        @RequestMapping("getVehicleInMap")
        ReturnModel getVehicleInMap(@RequestBody String vehicleIds, @RequestParam(required=false) String bounds);
    }
    
    • 客户端配置
    @FeignClient(/**客户端名称 对应nacos注册中心的服务名称*/name = NacosServerHostConstant.OBD_PORTAL_NAME,/**调用接口异常,快速失败了*/fallback= FeignLocationServiceFallback.class,/**config类*/configuration = OAuth2FeignAutoConfig.class)
    //实现IFeignLocationService接口
    public interface FeignLocationService extends IFeignLocationService{
    }
    
    • fallback类
    //注册spring bean
    @Component
    //重载父类映射路径  避免发生路径冲突
    @RequestMapping("feign/locationback")
    public class FeignLocationServiceFallback implements FeignLocationService {
    	@Override
    	public ReturnModel getVehicleInMap(String vehicleIds,String bounds) {
    		return ReturnModel.feignFail();
    	}
    	@Override
    	public ReturnModel getVehicleRealTimeStatus(int vehicleId) {
    		return ReturnModel.feignFail();
    	}
    	@Override
    	public ReturnModel getVehicleLocation(String vehicleIds) {
    		return ReturnModel.feignFail();
    	}
    }
    
    • config类
    public class OAuth2FeignAutoConfig {
    	//获取Auth2token类
    	private CloudTokenService cloudTokenService;
    	public OAuth2FeignAutoConfig(CloudTokenService cloudTokenService) {
    		this.cloudTokenService = cloudTokenService;
    	}
    	//feign请求拦截器
    	@Bean
    	public RequestInterceptor OAuth2FeignRequestInterceptor() {
    		return new OAuth2FeignRequestInterceptor(cloudTokenService);
    	}
    }
    
    • Fegin控制器实现类(服务端)
    @RestController
    public class FeignLocationController extends ClientBaseController implements IFeignLocationService{
        @Autowired
        private VehicleMonitorService vehicleMonitorService;
        
        /**
         * 获取矩形区域内的车辆信息
         * @param organizeId  机构Id
         * @param bounds 矩阵  右上角精度,右上角维度|左下角精度,左下角维度
         * @return
         */
        @Override
        public ReturnModel getVehicleInMap(String vehicleIds, String bounds) {
            ReturnModel model = new ReturnModel();
            model.setData(vehicleMonitorService.getVehicleInMap(JSONArray.parseArray(vehicleIds), bounds));
            return model;
        }
    }
    

    网关项目

    • 路由配置
    server:
      port: 8080 #服务端口号
      tomcat:
        max-http-form-post-size: 20971520
    spring:
      profiles:
        active:
        - prod
      application:
      	#微服务注册名称
        name: obd-gateway
      cloud:
        gateway:
          #默认过滤器 处理跨域
          default-filters:
            - DedupeResponseHeader=Access-Control-Allow-Origin, RETAIN_UNIQUE
          globalcors:
            cors-configurations:
              '[/**]':
                allowedHeaders: "*"
                allowedOrigins: "*"
                allowedMethods: "*"
          discovery:
            locator:
              enabled: true
          routes:
          #基于服务发现配置 portal
          - id: portal
          	#lb 负载均衡   obd-portal 调用微服务的名称
            uri: lb://obd-portal
            predicates:
            - Path=/obd-portal/**
            filters:
            - StripPrefix=1 #应用路由截掉路径的第一部分前缀
            - name: Hystrix #熔断降级
              args:
                name: fallbackcmd
                fallbackUri: forward:/hystrixFallback?a=test
    # 限流配置
    #        - name: RequestRateLimiter
    #          args:
    # 速率
    #            redis-rate-limiter.replenishRate: 10
    #容量
    #            redis-rate-limiter.burstCapacity: 20 
                        #基于服务发现配置 manage
           #基于服务发现配置 zhb
          - id: zhb
            uri: lb://obd-zhb
            predicates:
            - Path=/obd-zhb/**
            filters:
            - StripPrefix=1 #应用路由截掉路径的第一部分前缀
            - name: Hystrix #熔断降级
              args:
                name: fallbackcmd
                fallbackUri: forward:/hystrixFallback?a=test
                        #基于服务发现配置 zhbManage
          - id: zhbManage
            uri: lb://obd-zhb-manage
            predicates:
            - Path=/obd-zhb-manage/**
            filters:
            - StripPrefix=1 #应用路由截掉路径的第一部分前缀     
    

    接收终端数据项目

    • 启动socket项目
    @Component
    @Sharable
    @Slf4j
    public class OBDServer {
    	@Autowired
    	private MessageHandler messageHandler;
    	@Autowired
    	private TerminalAuthHandler terminalAuthHandler;
    	@Autowired
    	private TerminalCommonResponse terminalCommonResponse;
    	@Autowired
    	private TerminalRegisterHandler terminalRegisterHandler;
    	public void bind(int port) {
    		log.info("OBDNettySocket启动 port="+port);
    		//创建NIO线程组 实际上是reactor线程组
    		//用于接收客户端连接
    		EventLoopGroup boosGroup = new NioEventLoopGroup();
    		//用于进行SocketChnnel的网络读写
    		EventLoopGroup workerGroup = new NioEventLoopGroup();
    		try {
    			//启用nio服务对象
    			ServerBootstrap b = new ServerBootstrap();
    			//绑定nio线程
    			b.group(boosGroup, workerGroup)
    				//设置channel 为NioServerSocketChannel 对应java nio中的ServerSocketChannel
    				.channel(NioServerSocketChannel.class)
    				//设置NioServerSocketChannel的TCP参数 backlog
    				.option(ChannelOption.SO_BACKLOG, 1024)
    				//绑定nio事件处理类 作用类似于reactor模式中的handler
    				.childHandler(new ChannelInitializer<SocketChannel>() {
    					@Override
    					protected void initChannel(SocketChannel ch) throws Exception {
    						ch.pipeline().addLast(new OBDNettyMessageDecoder());
    						ch.pipeline().addLast(new OBDNettyMessageEncoder());
    						ch.pipeline().addLast(terminalRegisterHandler);
    						ch.pipeline().addLast(terminalAuthHandler);
    						ch.pipeline().addLast(terminalCommonResponse);
    						ch.pipeline().addLast(new HeartBeatHandler());
    						//必须放到最后
    						ch.pipeline().addLast(messageHandler);
    					}
    				});
    			//绑定端口 同步等待成功
    			ChannelFuture f = b.bind(port).sync();
    			//等待服务监听端口关闭
    			f.channel().closeFuture().sync();
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}finally {
    			log.info("OBDNettySocket关闭 port="+port);
    			//优雅退出 释放线程资源
    			boosGroup.shutdownGracefully();
    			workerGroup.shutdownGracefully();
    		}
    	}
    	
    }
    
    • 终端注册
    @Component
    @Sharable
    @Slf4j
    public class TerminalRegisterHandler  extends BaseHandler{
    	/**
    	 * 终端注册 256
    	 */
    	 private final int  TERMINAL_REGISTER = 0x0100;
    	 /**
    	  * 终端注册 应答 33024
    	  */
    	 private final int TERMINAL_REGISTER_RESPONSE = 0x8100; 
    	 @Autowired
    	 private CheckTerminalIdIsCanUseService  checkTerminalIdIsCanUseService;
    	@Override
    	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    		byte[] array = (byte[]) msg;
    		int messageId = OBDUtil.getIntFromODBIntArray(array, 0, 2);
    		if(messageId == TERMINAL_REGISTER) {
    			String terminalId = OBDUtil.getTerminalNumber(array);
    			//失败
    			byte status = 1;
    			String authCode = null;
    			if(checkTerminalIdIsCanUseService.check(terminalId)) {
    				status = 0;
    				//认证码  用于终端认证 目前没有使用
    				authCode = OBDConstant.TERMINAL_REGISTER_AUTH_CODE;
    				log.info("注册成功:terminalId="+terminalId);
    			}else {
    				log.info("注册失败:terminalId="+terminalId);
    			}
    			//发送注册结果
    			ctx.writeAndFlush(getTerminalRegisterResponse(array,new byte[] {array[10],array[11]}, status,authCode));
    			
    		}
    		ctx.fireChannelRead(msg);
    	}
    
    • 终端认证
    @Component
    @Sharable
    @Slf4j
    public class TerminalAuthHandler extends BaseHandler{
    	/**
    	 * 终端鉴权 258
    	 */
    	 private final static int  TERMINAL_AUTH = 0x0102;
    	 /**
    	  * 鉴权终端是否可用服务类
    	  */
    	 @Autowired
    	 private CheckTerminalIdIsCanUseService  checkTerminalIdIsCanUseService;
    	@Override
    	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    		byte[] array = (byte[]) msg;
    		//消息id
    		int messageId = OBDUtil.getIntFromODBIntArray(array, 0, 2);
    		if(messageId == TERMINAL_AUTH) {
    			//终端号
    			String terminalId = OBDUtil.getTerminalNumber(array);
    			//失败
    			byte status = 1;
    			//认证成功
    			if(checkTerminalIdIsCanUseService.check(terminalId)) {
    				status = 0;
    				//没有保存链路信息
    				if(SocketConfig.getSocketModel(terminalId) == null) {
    					//心跳检查定时任务
    					HeartBeatTask task = new HeartBeatTask(terminalId);
    					ctx.executor().schedule(task, OBDConstant.HEART_BEAT_INTERVAL, TimeUnit.MILLISECONDS);
    				}
    				//保存链路信息
    				SocketConfig.putSocket(terminalId, new SocketModel(ctx));
    				log.info("认证成功:terminalId="+terminalId);
    			}else {
    				log.info("认证失败:terminalId="+terminalId);
    			}
    			//返回认证结果
    			ctx.writeAndFlush(OBDCommonResponseUtil.getOBDCommonResponse(array,new byte[] {array[10],array[11]}, status));
    			
    		}else {
    			ctx.fireChannelRead(msg);
    		}
    	}
    	
    }
    
    • 消息处理
    	@Component
    @Sharable
    @Slf4j
    public class MessageHandler extends BaseHandler{
    	@Autowired
    	private DealMessageService dealMessageService;
    	@Override
    	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    		byte[] array = (byte[]) msg;
    		int messageId = OBDUtil.getIntFromODBIntArray(array, 0, 2);
    		String terminalId = OBDUtil.getTerminalNumber(array);
    		//log.info("收到终端消息:terminalId="+terminalId+"  messageId="+messageId);
    		SocketModel socketModel = SocketConfig.getSocketModel(terminalId);
    		//有链路信息  说明终端设备认证
    		if(socketModel != null) {
    			//处理消息
    			dealMessageService.dealMessage(String.valueOf(messageId),array);
    			socketModel.resetHeartNoRespCount();
    			ctx.writeAndFlush(OBDCommonResponseUtil.getOBDCommonResponse(array, new byte[] {array[10],array[11]}, (byte)0));
    		}else {
    			System.out.println(messageId);
    			log.info("终端未认证  terminalId="+terminalId);
    			//没有发现终端
    			ctx.writeAndFlush(OBDCommonResponseUtil.getOBDCommonResponse(array,new byte[] {array[10],array[11]}, (byte)5));
    		}
    	}
    	@Override
    	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    		System.out.println(ctx);
    		ctx.close();
    		cause.printStackTrace();
    		log.error("链路异常关闭");
    	}
    }
    

    轨迹解析服务

    • 从kafa中获取数据
    @Slf4j
    @Component
    public class KafkaConsumer {
    	@Autowired
    	private GpsVehicleTraceService gpsVehicleTraceService;
    	@Autowired
    	private TerminalNumberVehicleService terminalNumberVehicleService;
    	@Autowired
    	private RedisClient redisClient;
    	//处理轨迹上传数据
    	@KafkaListener(id="gpsLocation1",topics= KafkaTopicConstant.GPS_LOCATION)
    	public void onGPSLocation(ConsumerRecord<String, byte[]> record) {
    		//协议解析
    		OBDModel obd = ProtocolAnalysis.parse(record.value());
    		if(obd != null) {
    			String terminalNumber = obd.getTerminalNumber();
    			//获取绑定车辆信息
    			Integer vehicleId = terminalNumberVehicleService.getVehicleIdByTerminalNumber(terminalNumber);
    			if(vehicleId != null) {
    				PositionModel positionModel = (PositionModel)obd.getBody();
    				if(positionModel != null) {
    					//处理轨迹
    					gpsVehicleTraceService.dealGpsVehicle(vehicleId, positionModel,false);
    				}
    				
    			}else {
    				log.info("未找到绑定信息 terminalNumber ="+terminalNumber);
    			}
    		}
    	}
    	//轨迹补传处理
    	@KafkaListener(id="gpsLocationBeath1",topics=KafkaTopicConstant.GPS_LOCATION_BEATCH)
    	public void onGPSLocationBeath(ConsumerRecord<String, byte[]> record) {
    		OBDModel obd = ProtocolAnalysis.parse(record.value());
    		if(obd != null) {
    			String terminalNumber = obd.getTerminalNumber();
    			Integer vehicleId = terminalNumberVehicleService.getVehicleIdByTerminalNumber(terminalNumber);
    			if(vehicleId != null) {
    				LocationBatchModel positionBatch = (LocationBatchModel)obd.getBody();
    				if(positionBatch != null) {
    					List<PositionModel> list = positionBatch.getList();
    					if(list != null && !list.isEmpty()) {
    						for (PositionModel positionModel : list) {
    							gpsVehicleTraceService.dealGpsVehicle(vehicleId, positionModel,true);
    						}
    					}
    				}
    			}
    		}
    	}
    	//终端注册
    	@KafkaListener(id="terminalRegister",topics=KafkaTopicConstant.TERMINAL_REGISTER)
    	public void onTerminalRegister(ConsumerRecord<String, byte[]> record) {
    		ProtocolAnalysis.parse(record.value());
    	}
    	//终端注销
    	@KafkaListener(id="terminalUnRegister",topics=KafkaTopicConstant.TERMINAL_UNREGISTER)
    	public void onTerminalUnRegister(ConsumerRecord<String, byte[]> record) {
    		ProtocolAnalysis.parse(record.value());
    	}
    
    	/**
    	 * 查询终端参数
    	 */
    	@KafkaListener(id="terminalAnswer",topics=KafkaTopicConstant.TERMINAL_ANSWER)
    	public void getTerminalAnswer(ConsumerRecord<String, byte[]> record){
    		OBDModel obd = ProtocolAnalysis.parse(record.value());
    		if(obd != null){
    			String terminalNumber = obd.getTerminalNumber();
    			TerminalAnswerData answerData = (TerminalAnswerData)obd.getBody();
    			List<TerminalAnswerValue> answerValue = (List<TerminalAnswerValue>)answerData.getValue();
    			redisClient.setValue(RedisKeyPreConstant.TERMINAL_DATA, terminalNumber, answerValue);
    		}
    	}
    }
    
    • 协议解析类
    public class ProtocolAnalysis {
    	//协议解析类map
    	private final static Map<Integer,DealMessageService> dealMessageServiceMap = new HashMap<>();
    	static {
    		//初始化
    		dealMessageServiceMap.put(OBDConstant.MessageId.TERMINAL_AUTH, new TerminalAuthDealMessageServiceImpl());
    		dealMessageServiceMap.put(OBDConstant.MessageId.POSSITION, new PositionBaseDealMessageServiceImpl());
    		dealMessageServiceMap.put(OBDConstant.MessageId.TERMINAL_GENER_RESPONSE, new TerminalGeneralResponseDealMessageServiceImpl());
    		dealMessageServiceMap.put(OBDConstant.MessageId.POSSITION_BATCH, new PossitionBatchDealMassageServiceImpl());
    		dealMessageServiceMap.put(OBDConstant.MessageId.TERMINAL_REGISTER, new TerminalRegisterDealMessageServiceImpl());
    		dealMessageServiceMap.put(OBDConstant.MessageId.TERMINAL_ANSWER, new TerminalAnswerDealMessageImpl());
    		dealMessageServiceMap.put(OBDConstant.MessageId.ALARM_INFO, new AlarmDealMessageServiceImpl());
    	}
    //	public static void main(String[] args) {
    //		byte[] data = new byte[] {0, 1, 0, 5, 1, 65, 68, 120, 116, -107, 0, 33, 0, 1, -126, 7, 0, 60};
    //		
    //		OBDModel obdModel =parse(data);
    //		System.out.println(obdModel);
    //		System.out.println(Integer.toHexString(obdModel.getMessageId()));
    //	}
    	
    	public static OBDModel parse(byte[] chars) {
    		//转化为整行  
    		int[] dataArray =  new int[chars.length];
    		for(int i=0;i<dataArray.length;i++) {
    			if(chars[i] < 0 ) {
    				//数据修正 传输数据为无符号数
    				dataArray[i] = OBDUtil.obdDataCorrection(chars[i]);
    			}else {
    				dataArray[i] = chars[i];
    			}
    		}
    
    		OBDModel obdModel = new OBDModel();
    		//消息 ID 占用两个字节 高八位0 低八位 1
    		obdModel.setMessageId(OBDUtil.getIntFromODBIntArray(dataArray, 0, 2));
    		//消息体属性 占用两个字节 高八位2 低八位3
    		obdModel.setBodyDescribe(getBodyDescribe(OBDUtil.getIntFromODBIntArray(dataArray, 2, 2)));
    		//终端号 占用 4~9位
    		obdModel.setTerminalNumber(getTerminalNumber(dataArray));
    		//消息流水号 占用两个字节 高八位10低八位11
    		obdModel.setSerialNumber(OBDUtil.getIntFromODBIntArray(dataArray, 10, 2));
    		int bodyIndex = 12;
    		if(obdModel.getBodyDescribe().getIsSubpackage()) {
    			//消息总包数 占用两个字节 高八位12低八位13
    			obdModel.setTotalPackage(OBDUtil.getIntFromODBIntArray(dataArray, 12, 2));
    			//包序号 占用两个字节 高八位14低八位15
    			obdModel.setPackageNum(OBDUtil.getIntFromODBIntArray(dataArray, 14, 2));
    			bodyIndex += 4;
    		}
    		int bodyLength = obdModel.getBodyDescribe().getBodyLength();
    		if(bodyLength>0) {
    			if(OBDConstant.TOP_MIN_LENGTH + bodyLength <= dataArray.length) {
    				int[] bodyIntArray = new int[bodyLength];
    				System.arraycopy(dataArray, bodyIndex, bodyIntArray, 0, bodyLength);
    				//设置消息体
    				obdModel.setBody(getBody(obdModel.getMessageId(),bodyIntArray));
    			}
    		}
    		return obdModel;
    	};
    	/**
    	 * 获取body信息
    	 */
    	public static Object getBody(int messageid,int[] bodyArray) {
    		//是否支持该消息类型的解析
    		if(dealMessageServiceMap.containsKey(messageid)) {
    			return dealMessageServiceMap.get(messageid).getBody(bodyArray);
    		}else {
    			return Arrays.toString(bodyArray);
    		}
    	}
    	
    	/**
    	 * 获取终端号 BCD[6] 4-9
    	 */
    	public static String getTerminalNumber(int[] dataArray) {
    		String result = "";
    		for(int i=4;i<=9;i++) {
    			result += OBDUtil.getBCDStr(dataArray[i]);
    		}
    		return result;
    	}
    	
    	/**
    	 * 获取消息体属性
    	 */
    	public static BodyDescribe getBodyDescribe(int bodyInt) {
    		BodyDescribe bodyDescribe = new BodyDescribe();
    		//低9为字节表示长度
    		bodyDescribe.setBodyLength(bodyInt % (1<<10));
    		//第10-12字节全为0表示消息不加密
    		bodyDescribe.setIsEncryption((bodyInt&((1<<10)+(1<<11)+(1<<12)))!=0);
    		//第10字节为1表示RSA加密
    		bodyDescribe.setEncryptionType(OBDUtil.checkIntBitIsOne(bodyInt, 10)?1:null);
    		//第13位为1表示分包传输
    		bodyDescribe.setIsSubpackage(OBDUtil.checkIntBitIsOne(bodyInt, 13));
    		return bodyDescribe;
    	}
    	
    	/**
    	 * 验证码校验
    	 */
    	public static boolean check(int[] dataArray) {
    		int result = dataArray[dataArray.length-1];
    		int calR = dataArray[0] ^ dataArray[1];
    		for(int i=2;i<dataArray.length-1;i++) {
    			calR = calR^dataArray[i];
    		}
    		return calR == result;
    	}
    
    • 认证消息类型解析
    public class TerminalAuthDealMessageServiceImpl implements DealMessageService{
    	@Override
    	public Object getBody(int[] bodyArray) {
    		return new String(bodyArray,0,bodyArray.length);
    	}
    }
    
    展开全文
  • 大型项目架构设计

    2013-12-26 23:30:23
    1、大型项目架构设计 多设备 永不下线 实时性 大访问 大并发 分布广 网络安全 海量数据 2、解决方案 服务器优化 负载均衡 cdn 缓存技术 云计算 nosql 数据库优化 数据挖掘 3、CDN 内容分发系统...
    1、大型项目架构设计
    多设备
    永不下线
    实时性
    大访问
    大并发
    分布广
    网络安全
    海量数据
    2、解决方案
    服务器优化
    负载均衡
    cdn
    缓存技术
    云计算
    nosql
    数据库优化
    数据挖掘

    3、CDN 内容分发系统,网络提供商提供接口上传,静态资源
    4、
    iis7+fastcgi
    ngxin+fastcgi
    apache+fastcgi 4000并发  4G内存windows
    ngix+fastcgi  50000并发  4G内存windows
    5、负载均衡
        网络和硬件层
            物理层
            数据链路层
            网络层
        系统架构层
            操作系统
            数据库系统
            磁盘管理系统
        软件架构层
            资源IO
            通讯协议    
            内存操作
            数据管理
    6、最大并发
    10000用户
    1秒
    50*200=1000
    10
    8
    2000+10000
    14000
    18000
    8*1000=8000
    80000



    7、Laravel框架,创意项目高性能观察者模式,基于世界的开发
    8、raid 磁盘阵列,多个硬盘当一个硬盘使用,数据同步
            备份冗余
            读写效率
    展开全文
  • Springboot项目架构设计

    2021-03-03 13:03:38
    项目架构 理解阿里应用分层架构 superblog项目架构 结语 参考 本节是《Spring Boot 实战纪实》的第7篇,感谢您的阅读,预计阅读时长3min。智客工坊出品必属精品。 前言 关于架构的理解,一千个人心中有一...

    导航

    • 前言
    • 流水线
    • 架构的艺术
    • 项目架构
      • 理解阿里应用分层架构
      • superblog项目架构
    • 结语
    • 参考

    本节是《Spring Boot 实战纪实》的第7篇,感谢您的阅读,预计阅读时长3min。 智客工坊出品必属精品。

    前言

    关于架构的理解,一千个人心中有一千个哈姆莱特。这和项目经验和团队文化有很大关系。

      我想很多人其实对编程是有误解的。在中国古代提倡六艺,后面又提倡琴棋书画,这些都是才艺或者技艺。编程也是一门技艺,并没有大家想象的那么神秘。当我们通过书本学到一门编程语言的时候,往往只是学到了一些技巧,一些技术,但是要真正的成为行家,大拿,那就需要艺术了。在编程中,架构就是一门艺术。

    流水线

    1769年,英国人乔赛亚·韦奇伍德开办埃特鲁利亚陶瓷工厂,在场内实行精细的劳动分工,他把原来由一个人从头到尾完成的制陶流程分成几十道专门工序,分别由专人完成。这样一来,原来意义上的“制陶工”就不复存在了,存在的只是挖泥工、运泥工、扮土工、制坯工等等制陶工匠变成了制陶工场的工人,他们必须按固定的工作节奏劳动,服从统一的劳动管理。

      富士康的流水线工作机制,大家都不陌生。流水线实际上就是通过分工来提供生产效率。

      现代的软件项目,很少是一个人能够开发的。往往是一个团队协作完成。这个团队就就会报包括项目经理,UI设计师,前端技术,后端技术,DBA等。这些不同工种的相互协作,最终交互一款完整的软件。

      有了分工,专人专事,多人协作,就必须有一套约定俗称的项目开发流程,而这套流程其实规定了代码的组织,变量的命名等。这套流程中的代码组织继续规范和沉淀就变成了项目架构规范。

    架构的艺术

      架构是一门艺术,和开发语言无关。架构是整个项目的顶层设计。特别是在Devops的流行的今天,开发人员也要参与项目部署和运维的工作。站在项目架构的角度,架构不仅要考虑项目的开发,还要考虑项目的部署。 本文更多地聚焦于项目开发的架构,即中小型项目的搭建。

      架构的功能:

    • 方便团队分工
    • 职责分离
    • 可扩展
    • 重用
    • 方便排错

    理解阿里应用分层架构

      在国内Java应用方面,阿里应该是权威。在阿里《Java开发规范》中有一个推荐的分层。

     

     

    • 开放 API 层: 可直接封装 Service 接口暴露成 RPC 接口; 通过 Web 封装成 http 接口; 网关控制层等。

    • 终端显示层:各个端的模板渲染并执行显示的层。当前主要是 velocity 渲染,JS 渲染,JSP 渲染,移 动端展示等。

    • Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。

    • Service 层:相对具体的业务逻辑服务层。

    • Manager 层:通用业务处理层,它有如下特征:

      1) 对第三方平台封装的层,预处理返回结果及转化异常信息,适配上层接口。 2) 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理。 3) 与 DAO 层交互,对多个 DAO 的组合复用。

    • DAO 层:数据访问层,与底层 MySQL、Oracle、Hbase、OB 等进行数据交互。

    • 第三方服务:包括其它部门 RPC 服务接口,基础平台,其它公司的 HTTP 接口,如淘宝开放平台、支 付宝付款服务、高德地图服务等。

    • 外部数据接口:外部(应用)数据存储服务提供的接口,多见于数据迁移场景中。

    分层领域模型规约:

    • DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
    • DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。
    • BO(Business Object):业务对象,可以由 Service 层输出的封装业务逻辑的对象。
    • Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类 来传输。
    • VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。

    manager层的使用

      其它层的关系,大家都很容易理解。但是manager层和service层容易搞混。这也是初学者容易迷惑的地方,笔者也是反复揣摩和多次查阅了相关资料,有了一个比较合理的理解。

      笔者认为,controller调用service层,service层调用dao层(或Mapper层)。manager层出现的目的就是供其他service层使用,就是说service不能直接调用service,而是通过manager来做调用。

    • manager层是对service层的公共能力的提取。
    • manager层还可以下沉内部的common/helper等。

      笔者所在团队,Java项目的搭建也是参照的这套标准。但是没有放之四海而皆准的标准,在实际搭建中都会根据实际业务和团队文化而因地制宜的有所改造。

      这里值得注意的是manager层,我想可能很多项目是没有使用这个层的。或者直接使用的common/helper作为替代。

    superblog项目架构

      鉴于以上的阐述,在设计superblog的架构上就基本心中有数了。我们姑且将博客中的创作称为作品,这里就以作品创建为例来展示一下项目各层之间的调用关系。

    笔者把Superblog创作定义为以下类型:

    • Article(文章)
    • Weekly(周刊)
    • Solution(解决方案)

     

     

      以上这种分层和调用关系和开发语言没有太多关系,无论是C#还是Java都是适用的,这就想三层框架,mvc架构一样,是一种设计理念和设计模式。

      架构的关系明确之后,搭建框架就是水到渠成的事情。

      讲到这里,终于要轮到Springboot上场了。下面我们就以Springboot来搭建一套开发架构。

      本项目包含一个父工程super-blog和 blog-base, blog-pojo,blog-dao, blog-manager, blog-service,blog-web,blog-webapi。

    • blog-base,blog-pojo 为其他模块的公共模块,blog-base依赖blog-pojo;
    • 七个子模块都依赖父模块,blog-dao 依赖 blog-base;
    • blog-service 依赖 blog-dao,也就间接依赖 blog-base;
    • blog-web和blog-webapi是并列的。都依赖 blog-service,也就间接依赖blog-base和blog-dao。
    • blog-manager是blog-service中公共的业务层。

     

     

    模块的概念在不同语言之间叫法不同。在.NET Core项目中,每一层都是一个程序集,程序集之间通过引用来表达调用关系。在Springboot项目中,每一层都是一个模块,各个模块之间通过maven配置依赖关系。

    比如,以下配置就表示父工程下有哪些子模块。

    <!--声明子模块-->
        <modules>
            <module>blog-pojo</module>
            <module>blog-dao</module>
    		    <module>blog-service</module>
    		    <module>blog-manager</module>
    		    <module>blog-base</module>
    		    <module>blog-webapp</module>
    	</modules>
    

    而这个配置又表示了一种子模块之间的依赖关系。

    	<!-- 添加 blog-service 的依赖 -->
    		<dependency>
    			<groupId>com.zhike</groupId>
    			<artifactId>blog-service</artifactId>
    			<version>0.0.1-SNAPSHOT</version>
    		</dependency>
    

      值得注意的是,在Springboot之间通常使用单向依赖,避免产生相互引用。双向引用在编译阶段不会报错,但是运行时就会异常。

      按照惯例,也要将搭建好的项目架构展示一下:

     

     

    结语

      本文主要是从理论上对分层架构的进行了阐述。下一篇会讲解搭建springboot多模块的详细步骤。对于新手同学可以参考《零基础上手JavaWeb》 和《使用IDEA快速搭建一个SpringBoot项目》(详细),先尝试搭建一个最简单的Springboot项目。

    选择并聚焦到一点,把事情做到极致。

    参考:


    该系列往期文章

    展开全文
  • 【Unity3D】项目架构设计与开发管理

    千次阅读 2017-11-22 14:35:06
    Unity项目架构设计与开发管理笔者是观摩刘钢先生讲解的Unity项目架构设计与开发管理后所总结记录的。

    笔者是观摩刘钢先生讲解的Unity项目架构设计与开发管理后所总结记录的。 同样也推荐你去观摩以下。

    0×01 EmptyGO

    • 将所有的代码放到一个空的游戏对象中;
    • 使用GameObject.Find()来找到目标进行使用。

    架构设计的雏形实现,缺点是当我们的项目越来越大的时候难以灵活管理;不适合大型项目。

    0×02 Simple GameManager

    GameManager.Instance.playSound("menu");  
    
    • 它是把EmptyGO做成一个单例来使用;
    • 比较适合小型项目;
    • 缺点是单一文件过于庞大;
    • 不是即插即用。

    0×03 Manager of Managers

    • 使用一个主管理器自定义和管理所有的子管理器。
    • 子管理器作为单例使用,可以轻松地相互协作。

    MainManager

    EventManager:消息传递管理
    AudioManager:音效管理
    GUIManager:图形视图管理
    PoolManager:GO管理
    LevelManager: 关卡管理
    GameManager:核心机制管理
    SaveManager:游戏进度管理
    MenuManager:菜单行为动画管理

    0×04 MVCS(StrageIOC)

    优点: 将View和Model之间增加一个媒介层

    IBinder.Bind<Key>().To<Value>();
    

    MVCS Context Architecture

    0×05 MVVM(uFrame)

    优点:

    • 低耦合
    • 可重用性

    0×06 实体组件系统(ECS)

    在2018年,Unity又重点推荐ECS;ECS是一种编写代码的方式,专注于您正在解决的实际问题:组成游戏的数据和行为。其中心为EntityComponentSystem

    • Entity 是实例,作为承载组件的载体,也是框架中维护对象的实体.
    • Component 只包含数据,具备这个组件便具有这个功能.
    • System 作为逻辑维护,维护对应的组件执行相关操作.

    除了出于设计原因更好地接近游戏编程之外,使用ECS可以使您处于利用Unity的C#job系统和Burst Compiler的理想位置,让您充分利用当今的多核处理器。

    使用ECS,可以让你从面向对象转向数据导向设计,这意味着重用代码更容易,并且更容易让其他人掌握并做出贡献。

    -EOF-


    知识共享许可协议
    本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。文章欢迎转载,复制,共享,但请希望保留我的署名权霍兆坤;感谢你的阅读。
    展开全文
  • guozilanTK 企业级项目架构设计实例和用法。 由于涉及内容比较多,因此在 8月18号时通过 QQ 讨论组分享本文的内容。 缘由 如果你使用了类似 Maven 的依赖管理工具,本 分享...
  • 项目架构设计总结:基于阿里云搭建的轻量级架构 前言 从项目启动到现在差不多快有一年了,在这一年里经历了很多大的版本的改变,业务模式经过不断的磨合也逐渐稳定。在这个时候,总结一下之前项目的...
  • react 项目架构设计

    2019-07-26 11:08:28
    一般一个项目从零开始大家的时候都需要完善一些公共机制,基础插件的安装,目录结构的设计,页面组件的拆分等等........ 最开始接触前端的时候,是从简单的html、css、js开始的,当时盛行的WEB理念是结构样式行为相...
  • Unity项目架构设计

    2016-06-18 23:24:38
    unity 架构的一些架构的简要设计理念以及相关的一些插件管理介绍
  • 产品面临重构,架构方面的资料查了很多,在这里整体记录下觉的比较好的资料 设计模式六大原则 原文链接 单一原则 一个类应该仅有一个引起变换的原则。 个人解析:一个类不应该被各个地方当成不同的东西来使用,...
  • 结合实例谈项目架构设计

    千次阅读 2018-01-17 16:28:15
    作为一个移动端开发人员来讲,是很难接触到后端项目架构的,所幸,从2015年开始,负责部分管理工作,参与了项目架构相关的工作。项目从小到大,架构也越来越复杂,特别是最近做的一个跨国型项目,涉及到国内国外...
  • laravel-项目架构设计

    千次阅读 2018-05-25 18:32:27
    这里我先贴一个项目: 有时间在写我为什么这样做。 项目架构 1 :Route 模块化 appApi(Android ios) userRoute DescRoute webApp(小程序) userRoute DescRoute w...
  • 项目架构设计思考

    2017-08-02 11:06:59
    解耦的方式进行架构设计很有可能会导致不能按时完成开发工作,影响上线时间。 由于客户方本身也是技术出身,具备相当的专业知识,所以客户方也不希望在后期对系统进行重建或重构,导致了必须在系统架构时要...
  • Maven中型项目架构设计

    千次阅读 2016-05-16 11:44:36
    首先,前面几次学习已经学会了安装maven,如何创建maven项目等,最近的学习,终于有点进展了,搭建一下企业级多模块项目。 好了,废话不多说,具体如下: 首先新建一个maven项目,pom.xml的文件如下: 搭建多...
  • 互联网+和物联网由于发展的侧重点不同,在做架构设计上肯定有所不同。而以中小项目为主的物联网项目,其实更看重的,一是系统稳定可靠,能保证系统长期稳定的运行。本文主要介绍工业级物联网项目架构设计及实施。
  • Vue 项目架构设计与工程化实践

    千次阅读 2018-10-14 10:32:23
    一套很实用的架构设计 通过 cli 工具生成新项目 通过 cli 工具初始化配置文件 编译源码与自动上传CDN Mock 数据 反向检测server api接口是否符合预期 前段时间我们导航在开发一款新的产品,名叫 快言,是一个...
  • 分享到 ...本文算是一篇漫谈,谈一谈关于android开发中工程初始化的时候如何在初期我们就能搭建一个好的架构。本文先分析几个当今比较流行的android软件包,最后我们汲取其中觉得优秀的部分,搭建我们
  • 文章目录架构设计数据同步-解决方案mysql数据库建模HBase数据库建模 架构设计 数据同步-解决方案 mysql数据库建模 HBase数据库建模
  • 前后端别离的系统架构设计 RESTful作风API的设计与完成 系统的效劳化解耦 Go言语完成Webservice Go言语的Channel和并发模型的理论应用 应用Go言语原生模板完成Web UI的完成     下载地址:百度网盘...
  • Unity 项目架构设计与开发管理 我们在之前花了两篇文章对架构、框架和库进行了简单的介绍。 而我们到目前为止,已经写了十四个示例了。 目录如下: 我们并没有去为了写框架而写框架,而是一个个实际问题地解决,最后...
  • vkeyi项目架构设计总结

    千次阅读 2012-04-25 20:14:48
    总体概况:1.... 后台采用OSGi架构5. 数据库访问采用ibatis + MySql【另外一端物联网接口部分用到了Oracle】架构失误的地方:1. 通信接口上, HTTP + JSon的方式无问题, 但采用了一个会话过期的机制以防止非
  • 目前系统需要对app端提供接口,我想请教下,大家一般是怎么做的?提供给其他系统调用的接口是另外一个单独的工程?是同一个工程的话,感觉比较混乱,而且接口访问量会比web端大很多
  • 项目的整体架构 1、整体我所理解的架构图 Created with Raphaël 2.1.2 基础层: init初始化的一些配置文件,比如包含通用的html头尾结构 逻辑方法层 util :整个端需要暴露的一些全局变量,全局方法。声明...
  • Springboot+shiro+mybatis-plus+vue前后端分离项目架构设计

    千次阅读 热门讨论 2020-12-30 04:18:26
    根据公司要求,搭建个前后端分离的权限系统,根据目前技术技术水平,采用以下技术栈开发,以此写一份博客记录下构架的系统框架,同时希望能帮助因搭建系统架构不怎么会的小伙伴们,废话不多说,直接列出技术栈: ...
  • Jujube项目架构设计

    2004-10-25 16:24:00
    昨天完成了Jujube项目架构设计,今天在用Delphi将这个狂架实现,自认为设计的还不错,有些沾沾自喜了啦!!看来我要挨批了,在适当的时间我会将他公布,让大家讨论!

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 25,171
精华内容 10,068
关键字:

项目架构设计