草庐IT

记录--uniapp自定义相机 自定义界面拍照录像闪光灯切换摄像头

林恒 2023-03-28 原文

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

因公司业务需要,需要开发水印相机功能,而项目代码用的uniapp框架,App端只能简单调用系统的相机,无法自定义界面,在此基础上,只能开发自定义插件来完成功能(自定义原生插件,即是用原生代码来编写组件实现功能,然后供uniapp项目调用),经过半个月的研究和开发,完成了这款插件,以高度自由的形式提供了开发者相机自定义界面的需求,只需要在相机界面引入
            <!-- 相机原生插件 START -->

            <camera-view

                ref="cameraObj"

                class="camera_view"

                :defaultCamera="currentCamera"

                @receiveRatio="receiveRatio"

                @takePhotoSuccess="takePhotoSuccess"

                @takePhotoFail="takePhotoFail"

                @recordSuccess="recordSuccess"

                @recordFail="recordFail"

                @receiveInfo="onError"

                :style="'width:'+previewWidth+'px;height:'+previewHeight+'px;margin-left:-'+marginLeft+'px'"

                >

            </camera-view>

            <!-- 相机原生插件 END -->

这里建议宽高设置为全屏,然后在界面上自定义叠加自己的按钮文字等实现自己的界面功能,然后调用插件提供的api实现物理功能

// 拍照

takePhoto(){

    console.error("开始拍照")

    // 设置水印

    this.$refs.cameraObj.addWaterText({

        "date":this.tempDateStr || "",

        "logo":"·七彩云·|水印相机",

        "address":(this.showAddress ? this.address:""),

        "time":this.tempTimeStr || "",

        "week":this.weekDay || "",

        "remark":(this.showRemark ? this.remark:"")

    });

    // 调用拍照api

    this.$refs.cameraObj.takePhoto();

},

// 切换闪光灯

    switchFlash(){

        if(this.flashStatus === 0){

            this.flashStatus = 1;

            this.$refs.cameraObj.openFlash();

        }else{

            this.flashStatus = 0;

            this.$refs.cameraObj.closeFlash();

        }

    },

// 切换摄像头

switchCamera(){

    if(this.currentCamera === "0"){

        this.currentCamera = "1";

        this.$refs.cameraObj.openFront();

    }else{

        this.currentCamera = "0";

        this.$refs.cameraObj.openBack();

    }

},    

原生插件开发文档

Android / IOS 原生插件都有两种类型扩展

1、  Module 扩展 非 UI 的特定功能. ( 直白点说就是只注重功能 )

2、 Component 扩展 实现特别功能的 Native 控件. ( 侧重点在界面 )

比如我们想实现一个自定义的原生按钮,那就得扩展Component,因为需要有界面,而想实现一个提供各种api的插件,比如加减乘除算法等不需要界面显示,只有结果数据的,这种就可以用Module

附上链接: 前往下载插件和demo实例

一、Android原生插件的实现

首先android类继承uniapp的特殊类UniComponent

public class LuanQingCamera extends UniComponent<FrameLayout>

在initComponentHostView这个固定方法返回一个组件

@Override

    protected FrameLayout initComponentHostView(Context context) {

        // 我们自定义了一个FrameLayout的组件(为了方便后面扩展水印)

        FrameLayout frameLayout = new FrameLayout(context);

        // 创建一个SurfaceView用来承载摄像头预览

        SurfaceView surfaceView = new SurfaceView(context);

        // 添加到布局中

        frameLayout.addView(surfaceView);

        

        if (mHolder == null) {

            mHolder = surfaceView.getHolder();




            mHolder.addCallback(new SurfaceHolder.Callback() {

                @Override

                public void surfaceCreated(SurfaceHolder holder) {

                    // 检查权限 如果权限满足就将打开摄像头,初始化预览

                    checkPermission();

                }




                @Override

                public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {




                }




                @Override

                public void surfaceDestroyed(SurfaceHolder holder) {




                }

            });

        }




        return frameLayout;

    }

申请权限,android 6.0起需一些危险权限要动态申请,因此我们在使用摄像头前申请

    @UniJSMethod

    public void checkPermission() {

        Context mContent = mUniSDKInstance.getContext();

        if(mContent instanceof Activity){




            // 用于请求权限的列表

            List<String> permissions = new ArrayList<>();

            // 判断权限是否足够的标识变量

            boolean isEnoughPermission = true;




            // 权限检查和判断模块 START

            List<PermissionEntity> checkList = new ArrayList<>();

            checkList.add(new PermissionEntity(Manifest.permission.CAMERA,"摄像头相机权限"));

            checkList.add(new PermissionEntity(Manifest.permission.RECORD_AUDIO,"录音录制权限"));

            checkList.add(new PermissionEntity(Manifest.permission.WRITE_EXTERNAL_STORAGE,"文件读写权限"));




            for (PermissionEntity p : checkList){

                // 判断是否有权限

                boolean isHas = ActivityCompat.checkSelfPermission(mUniSDKInstance.getContext(), p.getPermissionName()) == PackageManager.PERMISSION_GRANTED;

                if (isHas) {

                    // 已经有权限(可能用户在设置中开启了)的话就把配置中的权限状态设置为已有权限

                    SharedData.setParam(mUniSDKInstance.getContext(),p.getPermissionName(),1);

                }




                // 权限状态: 0|无权限  1|有权限  2|已拒绝

                int status = (int) SharedData.getParam(mUniSDKInstance.getContext(),p.getPermissionName(),0);

                if(status == 0){

                    // 添加到权限请求列表

                    permissions.add(p.getPermissionName());

                    isEnoughPermission = false;

                }else if(status == 2){

                    isEnoughPermission = false;

                    backData("receiveInfo", 2003 ,"缺少"+p.getDescribe());

                }

            }




            // 如果权限足够了直接初始化相机

            if(isEnoughPermission){

                initCameraOption();

                return;

            }

            // 权限检查和判断模块 START




            if(permissions.size() > 0){

                EsayPermissions.with((Activity) mContent).permission(permissions).request(new OnPermission() {

                    @Override

                    public void hasPermission(List<String> granted, boolean isAll) {

                        if(isAll){

                            initCameraOption();

                        }else{

                            backData("receiveInfo", 2003 ,"缺少摄像头|录制录音|文件读写权限");

                        }

                    }




                    @Override

                    public void noPermission(List<String> denied, boolean quick) {

                        // 把已拒绝的权限记录,下次不再弹出权限申请,因为不这样做存在会被应用市场拒绝并下架的风险

                        for (String permission : denied){

                            // 用户拒绝

                            SharedData.setParam(mUniSDKInstance.getContext(),permission,2);

                        }

                        backData("receiveInfo", 2003 ,"未授予摄像头|录制录音|文件读写权限");

                    }

                });

            }

        }

    }

摄像头开始预览,显示可见的内容

// 开始预览

    @UniJSMethod

    public void startPreview() {

        try {

            if(mCameraCaptureSession != null){

                mCameraCaptureSession.stopRepeating();//停止之前的会话操作,准备切换到预览画面

                mCameraCaptureSession.close();//关闭之前的会话

                mCameraCaptureSession = null;

            }



            //创建预览请求

            mPreviewCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

            // 设置自动对焦模式

            mPreviewCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);




            //设置Surface作为预览数据的显示界面

            mPreviewCaptureRequestBuilder.addTarget(mHolder.getSurface());

            //创建相机捕获会话,第一个参数是捕获数据的输出Surface列表,第二个参数是CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行

            mCameraDevice.createCaptureSession(Arrays.asList(mHolder.getSurface(),mImageReader.getSurface()),new CameraCaptureSession.StateCallback() {

                @Override

                public void onConfigured(CameraCaptureSession session) {

                    mCameraCaptureSession = session;

                    try {

                        //开始预览

                        mPreviewCaptureRequest = mPreviewCaptureRequestBuilder.build();

                        UniLogUtils.e("初始化开启预览");

                        //设置反复捕获数据的请求,这样预览界面就会一直有数据显示

                        mCameraCaptureSession.setRepeatingRequest(mPreviewCaptureRequest, null, null);

                    } catch (CameraAccessException e) {

                        e.printStackTrace();

                    }

                }




                @Override

                public void onConfigureFailed(CameraCaptureSession session) {

                    UniLogUtils.e("预览失败");

                }

            }, null);

        } catch (CameraAccessException e) {

            e.printStackTrace();

        }

    }

执行拍照功能

@UniJSMethod

    public void takePhoto() {

        UniLogUtils.e("准备开始拍照");

        if (mCameraDevice == null) return;

        try {

            imageFileName = System.currentTimeMillis() + ".jpg";




            //首先我们创建请求拍照的CaptureRequest

            CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);




            Context context = mUniSDKInstance.getContext();

            if(context instanceof Activity){

                Activity activity = (Activity)mUniSDKInstance.getContext();

                //获取屏幕方向

                int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();

                //一个 CaptureRequest 除了需要配置很多参数之外,还要求至少配置一个 Surface(任何相机操作的本质都是为了捕获图像),

                captureBuilder.addTarget(mImageReader.getSurface());




                // 自动对焦

//                captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);

//              // 自动曝光开

                captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);

\


//                captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);

\


                // 这里有个坑,设置闪光灯必须先设置曝光

                if(flashState == 0){

                    captureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);

                }else{

                    captureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_SINGLE);

                }




//                captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);

//                captureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_SINGLE);

                captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));




                mCameraCaptureSession.stopRepeating();

                CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {

                    @Override

                    public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {

                        super.onCaptureCompleted(session, request, result);

                        UniLogUtils.e("拍照成功:");

                        backData("takePhotoSuccess", 200 ,"ok");

                        startPreview();

                    }




                    @Override

                    public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {

                        super.onCaptureFailed(session, request, failure);

                        UniLogUtils.e("拍照失败:");

                        backData("takePhotoFail", 2001 ,"拍照操作失败");

                    }

                };

                UniLogUtils.e("开始拍照");




                mCameraCaptureSession.capture(captureBuilder.build(), captureCallback, null);

            }

        } catch (CameraAccessException e) {

            e.printStackTrace();

        }

    }

二、IOS原生插件的实现

ios端相比较,更为简单

头部文件 .h

#import <AVFoundation/AVFoundation.h>

#import "DCUniComponent.h"

NS_ASSUME_NONNULL_BEGIN

@interface LQCamera : DCUniComponent

@end

NS_ASSUME_NONNULL_END

.m文件实现固定函数,并返回一个组件

- (UIView *)loadView {

    NSLog(@"插件日志:loadView");

    return [UIView new];

}

初始化一些摄像头参数

- (void)viewDidLoad {

    NSLog(@"插件日志:viewDidLoad");

    

    self.session = [[AVCaptureSession alloc] init];

    

    //创建一个AVCaptureMovieFileOutput 实例,用于将Quick Time 电影录制到文件系统

    self.movieOutput = [[AVCaptureMovieFileOutput alloc]init];

    //输出连接 判断是否可用,可用则添加到输出连接中去

    if ([self.session canAddOutput:self.movieOutput])

    {

        [self.session addOutput:self.movieOutput];

    }

    

    //     拿到的图像的大小可以自行设定

    //    AVCaptureSessionPresetHigh

    //    AVCaptureSessionPreset320x240

    //    AVCaptureSessionPreset352x288

    //    AVCaptureSessionPreset640x480

    //    AVCaptureSessionPreset960x540

    //    AVCaptureSessionPreset1280x720

    //    AVCaptureSessionPreset1920x1080

    //    AVCaptureSessionPreset3840x2160

    self.session.sessionPreset = AVCaptureSessionPreset1920x1080;

    

    //AVCaptureStillImageOutput 实例 从摄像头捕捉静态图片

    self.imageOutput = [[AVCaptureStillImageOutput alloc]init];

    //配置字典:希望捕捉到JPEG格式的图片

    self.imageOutput.outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};

    if ([self.session canAddOutput:self.imageOutput]) {

        [self.session addOutput:self.imageOutput];

    }

\


    self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

    NSError * error = nil;

    self.input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:&error];

    

    if (self.input) {

        [self.session addInput:self.input];

    }else{

        NSLog(@"Input Error:%@",error);

    }

\


    //预览层的生成

    self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];

    

    // 直接取用本组件的bounds来做定位,因为本组件的bounds是uniapp传过来的css宽高设置过的

    self.previewLayer.frame = self.view.bounds; //预览层填充视图

\


    // AVLayerVideoGravityResizeAspectFill 等比例填充,直到填充满整个视图区域,其中一个维度的部分区域会被裁剪

    // AVLayerVideoGravityResize 非均匀模式。两个维度完全填充至整个视图区域

    // AVLayerVideoGravityResizeAspect 等比例填充,直到一个维度到达区域边界

    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;

    [self.view.layer addSublayer:self.previewLayer];

\


    [self.session startRunning];

}

一些固定的标注写法

/// 前端更新属性回调方法

/// @param attributes 更新的属性

- (void)updateAttributes:(NSDictionary *)attributes {

    // 解析属性

    if (attributes[@"showsTraffic"]) {

//        _showsTraffic = [DCUniConvert BOOL: attributes[@"showsTraffic"]];

    }

}

\


/// 前端注册的事件会调用此方法

/// @param eventName 事件名称

- (void)addEvent:(NSString *)eventName {

    if ([eventName isEqualToString:@"mapLoaded"]) {

        

    }

}

\


/// 对应的移除事件回调方法

/// @param eventName 事件名称

- (void)removeEvent:(NSString *)eventName {

    if ([eventName isEqualToString:@"mapLoaded"]) {

        

    }

}

ios端回调原生方法

// 返回给前端的信息回调

// 向前端发送事件,params 为传给前端的数据 注:数据最外层为 NSDictionary 格式,需要以 "detail" 作为 key 值

- (void) returnFunc:(NSString *) func returnCode:(NSNumber *)code returnMess:(NSString *) message{

    NSString *imgUrl = self.imagePath ? self.imagePath : @"";

    NSString *vioUrl = self.videoPath ? self.videoPath : @"";

\


    [self fireEvent:func params:@{@"detail":@{@"code":code,@"message":message,@"videoPath":vioUrl,@"imagePath":imgUrl}} domChanges:nil];

}

拍照、录像[开始、停止]、闪光灯切换、摄像头镜头切换、设置水印内容等功能接口

// 下列为暴露出来的方法列表 START

// 通过 WX_EXPORT_METHOD 将方法暴露给前端

UNI_EXPORT_METHOD(@selector(openFlash))

// 开启闪光灯

- (void)openFlash {

    [self setFlashMode:AVCaptureFlashModeOn];

}

\


UNI_EXPORT_METHOD(@selector(closeFlash))

// 关闭闪光灯

- (void)closeFlash {

    [self setFlashMode:AVCaptureFlashModeOff];

}

\


UNI_EXPORT_METHOD(@selector(autoFlash))

// 自动闪光灯

- (void)autoFlash {

    [self setFlashMode:AVCaptureFlashModeAuto];

}

\


UNI_EXPORT_METHOD(@selector(openFront))

// 切换前置摄像头

- (void)openFront {

    [self switchCamer:AVCaptureDevicePositionFront];

}

\


UNI_EXPORT_METHOD(@selector(openBack))

// 切换后置摄像头

- (void)openBack {

    [self switchCamer:AVCaptureDevicePositionBack];

}

\


// 通过 WX_EXPORT_METHOD 将方法暴露给前端

UNI_EXPORT_METHOD(@selector(takePhoto:))

// 拍照

- (void)takePhoto:(NSDictionary *)options {

    // options 为前端传递的参数

    NSLog(@"IOS收到开始拍照请求");

    

    //获取连接

    AVCaptureConnection *connection = [self.imageOutput connectionWithMediaType:AVMediaTypeVideo];

\


    //程序只支持纵向,但是如果用户横向拍照时,需要调整结果照片的方向

    //判断是否支持设置视频方向

    if (connection.isVideoOrientationSupported) {

        

        //获取方向值

        connection.videoOrientation = [self currentVideoOrientation];

    }

\


    //定义一个handler 块,会返回1个图片的NSData数据

    id handler = ^(CMSampleBufferRef sampleBuffer,NSError *error)

                {

                    if (sampleBuffer != NULL) {

                        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:sampleBuffer];

                        UIImage *image = [[UIImage alloc]initWithData:imageData];

                        

                        [self returnFunc:@"takePhotoSuccess" returnCode:@200 returnMess:@"拍照成功"];

                        //重点:捕捉图片成功后,将图片传递出去

                        [self saveImage:image];

                    }else

                    {

                        NSLog(@"保存出错NULL sampleBuffer:%@",[error localizedDescription]);

                    }

                };

    

    //捕捉静态图片

    [self.imageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:handler];

}




UNI_EXPORT_METHOD(@selector(addWaterText:))

// 添加水印

- (void)addWaterText:(NSDictionary *)options{

    NSLog(@"接收到水印内容:%@",options);

    

    if(options[@"time"]){

        self.timeStr = options[@"time"];

    }

    if(options[@"date"]){

        self.dateStr = options[@"date"];

    }

    if(options[@"week"]){

        self.weekStr = options[@"week"];

    }

    if(options[@"address"]){

        self.addressStr = options[@"address"];

    }

    if(options[@"remark"]){

        self.remarkStr = options[@"remark"];

    }

    if(options[@"logo"]){

        self.logoStr = options[@"logo"];

    }

}

\


// 停止录制

UNI_EXPORT_METHOD(@selector(stopRecord))

- (void)stopRecord {

    NSLog(@"停止录像");

    [self.movieOutput stopRecording];

}




// 开始录制

UNI_EXPORT_METHOD(@selector(startRecord))

- (void)startRecord {

        NSLog(@"开始录像");




        // 获取当前视频捕捉连接信息,用于捕捉视频数据配置一些核心属性

        AVCaptureConnection * videoConnection = [self.movieOutput connectionWithMediaType:AVMediaTypeVideo];

        

        //判断是否支持设置videoOrientation 属性。

        if([videoConnection isVideoOrientationSupported])

        {

            //支持则修改当前视频的方向

            videoConnection.videoOrientation = [self currentVideoOrientation];

            

        }

        

        //判断是否支持视频稳定 可以显著提高视频的质量。只会在录制视频文件涉及

        if([videoConnection isVideoStabilizationSupported])

        {

            videoConnection.enablesVideoStabilizationWhenAvailable = YES;

        }

        

        

        AVCaptureDevice *device = self.input.device;

        

        //摄像头可以进行平滑对焦模式操作。即减慢摄像头镜头对焦速度。当用户移动拍摄时摄像头会尝试快速自动对焦。

        if (device.isSmoothAutoFocusEnabled) {

            NSError *error;

            if ([device lockForConfiguration:&error]) {

                

                device.smoothAutoFocusEnabled = YES;

                [device unlockForConfiguration];

            }else

            {

//                [self.delegate deviceConfigurationFailedWithError:error];

            }

        }

        

        //查找写入捕捉视频的唯一文件系统URL.

//        self.outputURL = [self uniqueURL];

        NSLog(@"开始录像2");




        //在捕捉输出上调用方法 参数1:录制保存路径  参数2:代理

        [self.movieOutput startRecordingToOutputFileURL:[self outPutFileURL] recordingDelegate:self];

}

// 下列为暴露出来的方法列表 END

到此一款包含Android+IOS两端的Uniapp原生插件完成

附上链接: 前往下载插件和demo实例

效果图:

https://juejin.cn/post/7107058762673815566

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

有关记录--uniapp自定义相机 自定义界面拍照录像闪光灯切换摄像头的更多相关文章

  1. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  2. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  3. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  4. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  5. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

  6. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  7. ruby - 定义方法参数的条件 - 2

    我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano

  8. ruby - 如何在 Grape 中定义哈希数组? - 2

    我使用Ember作为我的前端和GrapeAPI来为我的API提供服务。前端发送类似:{"service"=>{"name"=>"Name","duration"=>"30","user"=>nil,"organization"=>"org","category"=>nil,"description"=>"description","disabled"=>true,"color"=>nil,"availabilities"=>[{"day"=>"Saturday","enabled"=>false,"timeSlots"=>[{"startAt"=>"09:00AM","endAt"=>

  9. ruby-on-rails - Rails 5 Active Record 记录无效错误 - 2

    我有两个Rails模型,即Invoice和Invoice_details。一个Invoice_details属于Invoice,一个Invoice有多个Invoice_details。我无法使用accepts_nested_attributes_forinInvoice通过Invoice模型保存Invoice_details。我收到以下错误:(0.2ms)BEGIN(0.2ms)ROLLBACKCompleted422UnprocessableEntityin25ms(ActiveRecord:4.0ms)ActiveRecord::RecordInvalid(Validationfa

  10. ruby - 获取模块中定义的所有常量的值 - 2

    我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c

随机推荐